Brak wyników.

SDK

Jak faktycznie używać WolfieAuth z kodu — quickstarty, route protection, czytanie claimów, plan gating, org switching, webhook handling. Copy-paste przepisy dla najczęstszych stacków.

SDK — buduj swoją apkę na WolfieAuth

Ta strona to home base developera. Jeśli integrujesz WolfieAuth w coś co sam piszesz (a nie wpinasz go w gotowy produkt typu WordPress, Portainer czy Grafana — te siedzą pod Integracjami), zacznij tutaj.

Strona ułożona w trzech warstwach:

  1. Matryca SDK — które paczki istnieją, co każda dostarcza, kiedy wybrać który rodzaj.
  2. Quickstarty — minimalne hello-world dla czterech najużywanych stacków (Next.js, SvelteKit, Laravel, Django).
  3. Wzorce — konkretne przepisy na rzeczy które będziesz robił codziennie: protect route, czytanie user info, gating po planie/permissionie, switching orgi, refresh handling, webhook verification.

Wybierz SDK

WolfieAuth dostaje się w dwóch smakach SDK. Template-based (@wolfieauth/sdk-*) przychodzą z full-screen login templates, sealed cookies i wbudowanym per-app paywall — drop in trzy pliki i masz kompletny flow signup → login → plan-picker → app. Backend SDK (wolfieauth/<lang> paczki) wpinają tylko OIDC handshake i pozwalają budować własne UI.

StackPaczkaRodzajCo dostarcza
JS / Node / przeglądarki@wolfieauth/sdk-coreCorePKCE client, sealed sessions, claims helpery, theme tokens
SvelteKit@wolfieauth/sdk-sveltekitTemplateWszystkie 5 templatów + Svelte 5 komponenty
Next.js / React@wolfieauth/sdk-reactTemplatecreateAuthHandlers, middleware, hook useWolfieAuth()
Laravelwolfieauth/sdk-laravelTemplateService provider, controllers, Blade views
Symfony / Syliuswolfieauth/sdk-syliusTemplateSymfony bundle, Twig templates, ShopUserProvisioner
Paywall (any JS)@wolfieauth/sdk-paywallAdd-onStripe checkout + entitlement reads + KSeF invoice issuance
Express@wolfieauth/expressBackendOIDC handshake + middleware requireAuth
Next.js (Auth.js)@wolfieauth/nextjsBackendAuth.js provider + standalone routes
Djangowolfieauth (PyPI)BackendURL routes + claims helpery + webhook verifier
Railswolfieauth gemBackendEngine routes + Devise integration
Laravelwolfieauth/laravelBackendSocialite driver, auto-create users, events
Symfonywolfieauth/symfonyBackendStandalone bundle bez Sylius dep
CakePHPwolfieauth/cakephpBackendAuthentication adapter
Gogithub.com/wolfieauth/goBackendStateless session middleware

Template vs backend — kiedy wybrać co:

  • Template gdy chcesz wszystko (login screens, signup, plan picker, account settings, error pages) gotowe od ręki. Custom CSS przez theme tokens; pełne template overrides przez copy-paste pliku który chcesz zmienić.
  • Backend gdy masz już swoje UI i potrzebujesz tylko podpięty OIDC handshake (button, callback route, session middleware). Większość istniejących apek bierze backend wariant.

Quickstarty

Każdy quickstart zakłada że już zarejestrowałeś apkę w /admin/clients/new i masz client_id + client_secret pod ręką.

Next.js / React (@wolfieauth/sdk-react)

pnpm add @wolfieauth/sdk-react @wolfieauth/sdk-core

.env:

WOLFIEAUTH_ISSUER=https://auth.wolfieguard.com
WOLFIEAUTH_CLIENT_ID=twojaorg-twojaapka
WOLFIEAUTH_CLIENT_SECRET=...
WOLFIEAUTH_REDIRECT_URI=http://localhost:3000/api/auth/callback
WOLFIEAUTH_SESSION_SECRET=  # dowolny 32+ znakowy random string

app/api/auth/[...wolfieauth]/route.ts:

import { createAuthHandlers } from '@wolfieauth/sdk-react/server';

const handlers = createAuthHandlers({
  // env vary podchwytywane automatycznie; opcje tylko dla overrides
});

export const GET = handlers.GET;
export const POST = handlers.POST;

middleware.ts:

export { default } from '@wolfieauth/sdk-react/middleware';
export const config = { matcher: ['/((?!api/auth|_next).*)'] };

I tyle wirowania. W dowolnym client component:

'use client';
import { useWolfieAuth } from '@wolfieauth/sdk-react';

export default function Header() {
  const { user, claims, signOut, signIn } = useWolfieAuth();
  if (!user) return <button onClick={() => signIn()}>Zaloguj się</button>;
  return <span>Cześć {user.name} ({claims.wolfieauth_org_slug})  <button onClick={signOut}>Wyloguj</button></span>;
}

W server component albo route handler:

import { getSession } from '@wolfieauth/sdk-react/server';

export default async function Dashboard() {
  const session = await getSession();
  if (!session) redirect('/api/auth/signin');
  return <h1>Witaj {session.claims.email}</h1>;
}

SvelteKit (@wolfieauth/sdk-sveltekit)

pnpm add @wolfieauth/sdk-sveltekit @wolfieauth/sdk-core

src/hooks.server.ts:

import { wolfieauthHandle } from '@wolfieauth/sdk-sveltekit/server';
export const handle = wolfieauthHandle();

src/routes/+layout.server.ts:

export const load = async ({ locals }) => ({
  user: locals.session?.user ?? null,
  claims: locals.session?.claims ?? null,
});

W dowolnym +page.svelte:

<script>
  let { data } = $props();
</script>

{#if data.user}
  <p>Cześć {data.user.name}<a href="/auth/logout">Wyloguj</a></p>
{:else}
  <a href="/auth/login">Zaloguj się</a>
{/if}

W +page.server.ts (server-side gating):

import { redirect } from '@sveltejs/kit';
import { isApproved } from '@wolfieauth/sdk-core/oidc/claims';

export const load = async ({ locals }) => {
  if (!locals.session) throw redirect(302, '/auth/login');
  if (!isApproved(locals.session.claims)) throw redirect(302, '/auth/pending');
  return { /* page data */ };
};

Laravel (wolfieauth/sdk-laravel)

composer require wolfieauth/sdk-laravel
php artisan vendor:publish --tag=wolfieauth-config

.env:

WOLFIEAUTH_ISSUER=https://auth.wolfieguard.com
WOLFIEAUTH_CLIENT_ID=twojaorg-twojaapka
WOLFIEAUTH_CLIENT_SECRET=...
WOLFIEAUTH_REDIRECT_URI="${APP_URL}/auth/wolfieauth/callback"

Routy auto-rejestrują się. W dowolnym controller:

use Illuminate\Support\Facades\Auth;

public function dashboard()
{
    $user   = Auth::user();
    $claims = session('wolfieauth.claims');
    return view('dashboard', compact('user', 'claims'));
}

W Blade template:

@auth
  Cześć {{ Auth::user()->name }} ({{ session('wolfieauth.claims.wolfieauth_org_slug') }})
  <a href="{{ route('wolfieauth.logout') }}">Wyloguj</a>
@else
  <a href="{{ route('wolfieauth.login') }}">Zaloguj się przez WolfieAuth</a>
@endauth

Route protection:

Route::middleware(['auth', 'wolfieauth.feature:wolfie-twojaapka,ksef_enabled'])->group(function () {
    Route::get('/invoices/ksef', [KsefController::class, 'index']);
});

Django (wolfieauth na PyPI)

pip install wolfieauth

settings.py:

INSTALLED_APPS = [..., 'wolfieauth']
MIDDLEWARE = [..., 'wolfieauth.middleware.WolfieAuthMiddleware']

WOLFIEAUTH = {
    'ISSUER':        'https://auth.wolfieguard.com',
    'CLIENT_ID':     'twojaorg-twojaapka',
    'CLIENT_SECRET': '...',
    'REDIRECT_URI':  'http://localhost:8000/auth/wolfieauth/callback/',
}

urls.py:

from django.urls import include, path
urlpatterns = [
    path('auth/wolfieauth/', include('wolfieauth.urls')),
    # …urls Twojej apki…
]

W view:

from django.contrib.auth.decorators import login_required
from wolfieauth import claims

@login_required
def dashboard(request):
    c = request.wolfieauth_claims
    if not claims.is_approved(c):
        return redirect('/auth/pending')
    return render(request, 'dashboard.html', {'claims': c})

W DRF view:

from rest_framework.permissions import IsAuthenticated
from wolfieauth.permissions import RequireFeature

class KsefView(APIView):
    permission_classes = [IsAuthenticated, RequireFeature('wolfie-twojaapka', 'ksef_enabled')]
    def get(self, request): ...

Wzorce

Przepisy poniżej pojawiają się prawie w każdej apce na WolfieAuth. Pisane stack-by-stack żebyś mógł skopiować ten który matchuje twojego.

1. Protect route

Wzorzec: middleware-level redirect do /auth/login jeśli brak sesji; page-level redirect do /auth/pending jeśli włączony approval gate. Templaty robią obie rzeczy automatycznie; backend SDK-i wymagają jednej linii.

// Next.js — middleware.ts wystarcza; page-level extra check:
const session = await getSession();
if (!session) redirect('/api/auth/signin');
if (!isApproved(session.claims)) redirect('/auth/pending');
// Laravel — middleware groups w routes/web.php:
Route::middleware(['auth', 'wolfieauth.approved'])->group(...);
# Django — decorator:
@login_required
@wolfieauth_required(approved=True)
def view(request): ...

2. Czytanie user info w kodzie

Zawsze czytaj z claimów, nigdy nie refetchuj z /userinfo per request — claimy są podpisane i scache-owane w session cookie, więc czytanie ich jest darmowe.

const { claims } = useWolfieAuth();
claims.email                       // '[email protected]'
claims.name                        // 'Pawel Wolfie'
claims.sub                         // 'clxxx...' — stabilne ID user-a w WolfieAuth
claims.wolfieauth_org_id           // aktywna org dla tego tokena
claims.wolfieauth_org_slug         // 'acme'
claims.wolfieauth_role_slug        // 'editor' / 'admin' / etc
claims.wolfieauth_permissions      // ['invoices.read', ...]
claims.wolfieauth_membership_kind  // 'MEMBER' | 'GUEST' | 'SPECIAL_ADMIN'

Pełen claim shape siedzi w @wolfieauth/sdk-core/oidc/claims.ts (TypeScript types) i w docu SSO & Sesje.

3. Plan i feature gating

Dwa helpery, dwie semantyki. Większość apek chce wariant active.

import { hasFeature, hasActiveFeature, hasActivePlan } from '@wolfieauth/sdk-core/oidc/claims';

// Workspace mode — czy TA org ma feature?
if (hasActiveFeature(claims, 'wolfie-twojaapka', 'ksef_enabled')) renderKsefButton();

// Union mode — czy user ma to GDZIEKOLWIEK? (cross-org upsell)
if (hasFeature(claims, 'wolfie-twojaapka', 'advanced_reports')) {
  showHint("Masz już to w innej orgi — przełącz kontekst żeby tu użyć.");
}

// Hard plan check
if (!hasActivePlan(claims, 'wolfie-twojaapka', 'premium')) showUpsell();

Shape entry w claims.wolfieauth_plans[]:

{
  "client_id":  "wolfie-twojaapka",
  "plan_slug":  "premium",
  "status":     "ACTIVE",
  "orgId":      "org_abc",
  "orgSlug":    "acme",
  "orgIsShadow": false,
  "features":   ["ksef_enabled", "advanced_reports"]
}

orgIsShadow: true znaczy personal 1-person workspace ze solo signup-u. Niektóre apki traktują shadow orgi tak samo jak prawdziwe; inne explicite upselluje userów do “stwórz prawdziwą orgę z teammates.”

4. Permission checks

Permissions są role-based i computed server-side przy mint-time tokenu — Twoja apka czyta z claim, brak roundtripu.

import { hasPermission } from '@wolfieauth/sdk-core/oidc/claims';
if (!hasPermission(claims, 'invoices.write')) return forbidden();
from wolfieauth import claims
if not claims.has_permission(c, 'invoices.write'):
    return HttpResponseForbidden()
use WolfieAuth\Sdk\Permissions\Permissions;
if (!Permissions::has($claims, 'invoices.write')) abort(403);

Dla permission discovery (które permissions ma zalogowany user, żeby narenderować menu), czytaj claims.wolfieauth_permissions bezpośrednio — to flat array.

5. Switching aktywnej orgi

Gdy user należy do wielu org, twoje “org switcher” UI powinno pozwolić mu zmienić kontekst bez pełnego re-handshake. Helper w każdym SDK-u:

// React
const { switchOrg } = useWolfieAuth();
await switchOrg('org_xyz');  // następny render widzi nowe claimy
// SvelteKit (z +server.ts action)
import { switchOrg } from '@wolfieauth/sdk-sveltekit/server';
await switchOrg(event, 'org_xyz');
\WolfieAuth\Sdk\Sessions\Session::switchOrg('org_xyz');
return redirect()->back();
from wolfieauth.session import switch_org
switch_org(request, 'org_xyz')
return redirect('/dashboard')

Pod hood: hituje GET /api/auth/me?orgId=…, refresh-uje session cookie, kolejny request widzi zaktualizowane wolfieauth_org_id.

6. Refresh handling

Zwykle nie musisz nic robić — middleware SDK silently refreshes near-expired sessions w tle. Dwa przypadki wymagają uwagi:

  • Long-lived background jobs (queue worker trzymający token przez 30 min) — wywołaj refreshIfNeeded() przed każdą inwokacją:

    await session.refreshIfNeeded();   // sdk-core
    
  • Czytanie claimów z refresh-a który wylądował mid-render — hooki React/Svelte emitują event claims-refreshed do zasubskrybowania jeśli UI musi reagować (rzadko).

7. Webhook handling

Każdy SDK dostarcza helper webhook.verify(). Zweryfikuj header X-Wolfie-Signature na każdym przychodzącym hooku przed parsem body:

# Django
from wolfieauth import webhook

@csrf_exempt
def hook(request):
    sig = request.META.get('HTTP_X_WOLFIE_SIGNATURE')
    if not webhook.verify(settings.WOLFIEAUTH_WEBHOOK_SECRET, sig, request.body):
        return HttpResponseForbidden('bad_signature')
    payload = json.loads(request.body)  # {id, event, timestamp, data}
// Express
import { verifyWebhook } from '@wolfieauth/sdk-core/webhooks';

app.post('/hooks/wolfieauth', express.raw({type:'application/json'}), (req, res) => {
  const sig = req.header('x-wolfie-signature');
  if (!verifyWebhook(process.env.WOLFIEAUTH_WEBHOOK_SECRET, sig, req.body)) {
    return res.status(403).send('bad_signature');
  }
  const payload = JSON.parse(req.body.toString());
  // …
});

Format headera: X-Wolfie-Signature: t=<unix>,v1=<hex>. HMAC-SHA256 nad f"{t}.{raw_body}" — przekaż raw bytes, NIE re-serialised JSON dict (Stripe-style). Replays starsze niż 5 minut powinny być odrzucone; helpery robią to automatycznie.

Najczęściej subskrybowane eventy:

EventKiedy odpala
user.createduser dopiero co zarejestrował się do Twojej apki pierwszy raz
subscription.activatedplan przeszedł z PENDING / INCOMPLETE w ACTIVE
subscription.cancelledplan przeszedł w CANCELLED (użyj active-feature helperów; nic dodatkowo)
org.suspendedcała org spauzowana (np. przez reseller cascade-suspend)
linked_account.createduser dopiero co zlinkował się do Twojej apki z innej

8. Custom UI bez templatów

Jeśli używasz backend SDK i renderujesz własne login screeny, oto cztery URL-e które będziesz potrzebował:

GET  /auth/wolfieauth/login            # SDK route — redirect do WolfieAuth /authorize
GET  /auth/wolfieauth/callback         # SDK route — wymienia code, ustawia sesję
GET  /auth/wolfieauth/logout           # SDK route — czyści sesję, hituje /session/end
GET  /api/public/clients/<twoj-client-id>/branding   # public endpoint — fetch JSON theme dla brand-owanego login button styling

Nie musisz budować login formy sam — siedzi pod auth.wolfieguard.com i themed przez theme tokens Twojego clienta. Po prostu malujesz entry button w swojej apce.

Te same nazwy helperów wszędzie

Każdy SDK eksportuje te same kanoniczne helpery żebyś mógł grep-ować przez stacki. Shape-y adaptują się pod idiomy języka, nazwy są stabilne.

TypeScript / JavaScript

import {
  isApproved, isPendingApproval, canApproveIn,
  getPlan, hasPlan, hasActivePlan, hasFeature, hasActiveFeature,
  hasPastDuePlan, isBundlePlan,
  isResellerOf, getResellerSeatStatus, isChildOrg,
} from '@wolfieauth/sdk-core/oidc/claims';

Python (Django)

from wolfieauth import claims

claims.has_feature(c, 'wolfie-twojaapka', 'ksef_enabled')        # union
claims.has_active_feature(c, 'wolfie-twojaapka', 'ksef_enabled') # workspace
claims.has_active_plan(c, 'premium', app_client_id='wolfie-twojaapka')
claims.plans_for_app(c, 'wolfie-twojaapka')
claims.has_permission(c, 'invoices.write')
claims.is_reseller_of(c, 'wolfie-twojaapka')
claims.can_approve_in(c)  # ['org_xyz', ...]

PHP (Laravel / Sylius)

use WolfieAuth\Sdk\Plans\Plans;
use WolfieAuth\Sdk\Resellers\Resellers;

if (Plans::hasFeature($claims, 'wolfie-twojaapka', 'ksef_enabled')) { ... }
if (Plans::hasActivePlan($claims, 'wolfie-twojaapka', 'premium'))   { ... }
if (Resellers::isResellerOf($claims, 'wolfie-twojaapka'))           { ... }

Go

import "github.com/wolfieauth/go/claims"

if claims.HasFeature(c, "wolfie-twojaapka", "ksef_enabled") { ... }

Workspace mode vs union mode

User może należeć do wielu org; claims.wolfieauth_plans[] niesie entries dla każdej aktywnej subskrypcji w każdej orgi. Dwie interpretacje:

  • Union mode (hasFeature) — czy user ma tę feature GDZIEKOLWIEK? Użyj dla upsell-u (“masz już Premium w innej orgi — przełącz kontekst żeby tu użyć”).
  • Workspace mode (hasActiveFeature) — czy user ma tę feature w aktualnie wybranej orgi? Użyj dla tenant-isolated apek gdzie każda org to twarda granica.

Każdy plan entry niesie orgId / orgSlug / orgIsShadow — active-* helpery filtrują po orgId === claims.wolfieauth_org_id przed sprawdzeniem features. Shadow orgi (1-person personal workspace ze solo signup-u) mają orgIsShadow: true żeby apki mogły opt to mix or exclude.

Error handling

SDK-i throw-ują albo zwracają błędy gdy coś idzie źle po stronie auth. Shape-y które spotkasz:

BłądCo się stałoCo zrobić
WolfieAuthError("token_expired")Access token wygasł i refresh zawiódłRedirect do /auth/login
WolfieAuthError("invalid_session")Session cookie tampered albo wygasłeWyczyść lokalną sesję, redirect do /auth/login
WolfieAuthError("approval_pending")User zalogowany ale org wymaga approvaluRedirect do /auth/pending
WolfieAuthError("network")WolfieAuth nieosiągalnyPokaż “auth jest down” page; istniejące sesje dalej działają via JWKS cache
HTTP 403 z Twojego własnego gating-uUser nie ma wymaganego claimPokaż forbidden page albo upsell

Najlepiej pozwól middleware SDK-a transparentnie obsłużyć pierwsze trzy (każdy template-based SDK to robi); tylko ostatnie dwa wymagają app-level UX.

Czytaj dalej

Ostatnia aktualizacja: