← Back to API Documentation Main Documentation

Authentication Flows

Created: Original date unknown
Updated: Tue 29 Jul 2025 07:36:15 CEST - Renamed from authentication-flows.md and added Jekyll front matter
Updated: Thu 11 Jun 2026 17:06:39 CEST - Password recovery flow and Supabase redirect allowlist documented
Updated: Thu 11 Jun 2026 21:37:14 CEST - Sign-up e-mail verification now redirects to window.location.origin; allowlist requirements extended
Updated: Thu 11 Jun 2026 21:45:34 CEST - Custom auth e-mail webhook documented; orphaned send-password-reset edge function removed; send-verification-email restricted to webhook payloads
Updated: Mon 15 Jun 2026 - Reconciled auth e-mail delivery + sign-up verification sections from local edits
Updated: Mon 06 Jul 2026 22:36:36 CEST - Auth e-mails now render the 6-digit OTP code alongside the action link (iOS verifyOTP path)
Updated: Tue 07 Jul 2026 08:45 CEST - Auth e-mails rebranded to the design system (shared layout, hosted logo, no red); Resend API errors now surface as hook failures
Document Version: 1.6 - Brand layout + Resend error propagation; OTP code added to auth e-mails; delivery section documents both verification paths (send-verification-email webhook); legacy send-password-reset function deleted
Security Classification: Internal Technical Documentation
Target Audience: Backend Developers, Frontend Developers, Security Engineers
Author: Paul Wisén

Plings relies on Supabase Auth and JWTs to secure the GraphQL API. The key points below are distilled from architecture/api-security-guidelines.md.

Login & Token Issuance

  1. User authenticates via email/password, magic-link or OAuth provider managed by Supabase.
  2. Supabase returns an access token (JWT) and a refresh token.
  3. The frontend stores tokens in memory (or secure storage on mobile) and attaches Authorization: Bearer <jwt> to every HTTP or WebSocket request.

Custom Claims Enrichment

An Auth webhook injects additional, namespaced claims so that Row-Level Security (RLS) can evaluate them efficiently:

{
  "role": "org_member",      // guest | org_member | manufacturer_issuer | system_owner
  "org_id": "acme-123",      // current tenant context (UUID)
  "org_role": "admin"        // member | admin | owner within the tenant
}

GraphQL Context Resolution

On each request the backend:

async def get_context(request):
    token = request.headers.get("Authorization", "").split("Bearer ")[-1]
    user  = resolve_user_from_token(token)  # Supabase helper
    return { "request": request, "user": user }

The user object is then available in every resolver for fine-grained checks.

Token Refresh

The client automatically uses Supabase’s refresh endpoint to rotate tokens before expiry. WebSocket connections include the latest token via the connectionParams payload and reconnect when refreshed.

Logout

Password Recovery Flow (Plings-Web)

  1. User requests a reset (AuthModal → “Forgot password?”). The app calls supabase.auth.resetPasswordForEmail(email, { redirectTo: window.location.origin + '/reset-password' }).
  2. The e-mail link signs the user in with a recovery session and redirects to /reset-password, where a new password is set via supabase.auth.updateUser({ password }). On success the user stays signed in and is taken to /dashboard.
  3. Safety net: AuthContext listens for the PASSWORD_RECOVERY auth event and forces navigation to /reset-password, even if Supabase falls back to the Site URL.
  4. Signed-in users can change their password from the profile menu (“Change password”). No current password is required (Supabase updateUser semantics).

Auth E-mail Delivery (Custom Branded E-mails)

All Supabase Auth e-mails (sign-up verification, password recovery, etc.) are delivered through a single edge function, supabase/functions/send-verification-email, wired up as the Auth send-email hook (hook_send_email_url in supabase/config.toml). The hook posts the user and an email_data object carrying both a token_hash and the plain 6-digit OTP token; the function builds the /auth/v1/verify?token=...&type=...&redirect_to=... action link from token_hash, picks a branded template by email_action_type (signup, recovery, or generic), and sends via Resend.

Every template renders both verification paths, because the two clients verify differently:

Notes:

Sign-Up E-mail Verification (Plings-Web)

AuthContext.signUp calls supabase.auth.signUp(..., { emailRedirectTo: window.location.origin + '/' }), so the verification link returns the user to whichever environment they signed up from (production, localhost, or a preview deploy) instead of always landing on https://plings.io/.

Required Supabase configuration

Auth → URL Configuration → Redirect URLs must include an entry for every environment and flow:

Password recovery:

Sign-up verification (origin root):

For Vercel preview deploys, add a wildcard entry matching the project’s preview domains, e.g. https://*-<team>.vercel.app/** — Supabase supports * and ** globs in this list.

If a redirect URL is missing from this allowlist, Supabase silently falls back to the Site URL: a password reset lands on the home page signed in without a set-password step, and a localhost sign-up verification bounces to production instead of the dev server.

Status: imported skeleton – expand with diagrams & edge cases (MFA, SSO) later.