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:
- Matryca SDK — które paczki istnieją, co każda dostarcza, kiedy wybrać który rodzaj.
- Quickstarty — minimalne hello-world dla czterech najużywanych stacków (Next.js, SvelteKit, Laravel, Django).
- 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.
| Stack | Paczka | Rodzaj | Co dostarcza |
|---|---|---|---|
| JS / Node / przeglądarki | @wolfieauth/sdk-core | Core | PKCE client, sealed sessions, claims helpery, theme tokens |
| SvelteKit | @wolfieauth/sdk-sveltekit | Template | Wszystkie 5 templatów + Svelte 5 komponenty |
| Next.js / React | @wolfieauth/sdk-react | Template | createAuthHandlers, middleware, hook useWolfieAuth() |
| Laravel | wolfieauth/sdk-laravel | Template | Service provider, controllers, Blade views |
| Symfony / Sylius | wolfieauth/sdk-sylius | Template | Symfony bundle, Twig templates, ShopUserProvisioner |
| Paywall (any JS) | @wolfieauth/sdk-paywall | Add-on | Stripe checkout + entitlement reads + KSeF invoice issuance |
| Express | @wolfieauth/express | Backend | OIDC handshake + middleware requireAuth |
| Next.js (Auth.js) | @wolfieauth/nextjs | Backend | Auth.js provider + standalone routes |
| Django | wolfieauth (PyPI) | Backend | URL routes + claims helpery + webhook verifier |
| Rails | wolfieauth gem | Backend | Engine routes + Devise integration |
| Laravel | wolfieauth/laravel | Backend | Socialite driver, auto-create users, events |
| Symfony | wolfieauth/symfony | Backend | Standalone bundle bez Sylius dep |
| CakePHP | wolfieauth/cakephp | Backend | Authentication adapter |
| Go | github.com/wolfieauth/go | Backend | Stateless 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-coreCzytanie claimów z refresh-a który wylądował mid-render — hooki React/Svelte emitują event
claims-refresheddo 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:
| Event | Kiedy odpala |
|---|---|
user.created | user dopiero co zarejestrował się do Twojej apki pierwszy raz |
subscription.activated | plan przeszedł z PENDING / INCOMPLETE w ACTIVE |
subscription.cancelled | plan przeszedł w CANCELLED (użyj active-feature helperów; nic dodatkowo) |
org.suspended | cała org spauzowana (np. przez reseller cascade-suspend) |
linked_account.created | user 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łąd | Co się stało | Co zrobić |
|---|---|---|
WolfieAuthError("token_expired") | Access token wygasł i refresh zawiódł | Redirect do /auth/login |
WolfieAuthError("invalid_session") | Session cookie tampered albo wygasłe | Wyczyść lokalną sesję, redirect do /auth/login |
WolfieAuthError("approval_pending") | User zalogowany ale org wymaga approvalu | Redirect do /auth/pending |
WolfieAuthError("network") | WolfieAuth nieosiągalny | Pokaż “auth jest down” page; istniejące sesje dalej działają via JWKS cache |
| HTTP 403 z Twojego własnego gating-u | User nie ma wymaganego claim | Pokaż 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
- Tryb reseller — gdy Twoja downstream apka sama hostuje sub-tenantów
- SSO dla apek 3rd-party — wpinanie WolfieAuth w apki których nie pisałeś
- SSO & Sesje — co siedzi na kablu
- Plany & Billing — modele cenowe i Stripe wiring
Ostatnia aktualizacja: