Guide

BFF Server Authentication

Authorization Code + PKCE is recommended for new server integrations. The browser receives only a 60-second one-time code; Authon tokens are returned only during the server-to-server exchange.

Recommended flow

Keep the PKCE verifier and state in an HttpOnly transaction cookie or server store. At the callback, validate state, exchange the code with a secret key, then create your application's own HttpOnly session.

1. Server creates state and a PKCE pair
2. Browser signs in to Authon in code mode
3. Authon returns a one-time code to the registered callback
4. Server exchanges the code using a secret key POST /v1/auth/token/exchange
5. Server creates its own HttpOnly session
app/auth/start/route.ts
import { createAuthonAuthorizationRequest } from '@authon/nextjs/server';

export async function POST() {
  const { authorization } = await createAuthonAuthorizationRequest({
    redirectUri: 'https:0
  });
  return Response.json(authorization, {
    headers: { 'Cache-Control': 'no-store' },
  });
}
sign-in.ts
import { Authon } from '@authon/js';

const authorization = await fetch('/auth/start', { method: 'POST' })
  .then((response) => response.json());
const authon = new Authon('pk_live_...', { sessionMode: 'bff' });
await authon.openSignIn(authorization);
app/api/auth/authon/callback/route.ts
import { handleAuthonAuthorizationCallback } from '@authon/nextjs/server';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const result = await handleAuthonAuthorizationCallback({
    code: url.searchParams.get('code')!,
    state: url.searchParams.get('state')!,
    redirectUri: 'https:0
    secretKey: process.env.AUTHON_SECRET_KEY!,
  });

  await createApplicationSession(result.user, result);
  return Response.redirect(new URL('/app', url));
}
Keep AUTHON_SECRET_KEY and AUTHON_TRANSACTION_SECRET server-only. Register the callback URL in the dashboard using an exact full-URL match.

Legacy SPA token mode

Token mode remains supported for existing SPAs, but new BFF integrations should use code mode above. Token mode exposes Authon tokens to browser JavaScript.

frontend.ts
import { Authon } from '@authon/js';

const authon = new Authon('pk_live_...');

authon.on('signedIn', async (user) => {
  const authonToken = authon.getToken();

  // Send token to your backend
  const res = await fetch('/api/auth/authon', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: authonToken }),
  });

  const { serviceToken } = await res.json();
  // Use serviceToken for your API calls
});

await authon.openSignIn();

2. Backend: Verify token + issue session

Call Authon's token verification API from your backend to validate the token. If valid, create (or find) the user and issue your own service JWT.

routes/auth.ts
app.post('/api/auth/authon', async (req, res) => {
  const { token } = req.body;
  if (!token) return res.status(400).json({ error: 'Token required' });

  // 1. Verify the Authon token
  const verify = await fetch('https:1
    headers: { Authorization: 4 },
  });
  const { valid, user: authonUser } = await verify.json();

  if (!valid || !authonUser) {
    return res.status(401).json({ error: 'Invalid Authon token' });
  }

  2
  let user = await db.users.findByEmail(authonUser.email);
  if (!user) {
    user = await db.users.create({
      email: authonUser.email,
      name: authonUser.displayName,
      provider: 'authon',
    });
  }

  3
  const serviceToken = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.json({ serviceToken, user });
});

Token Verification API

GET/v1/auth/token/verify

Verifies an Authon JWT and returns user information. No API key required.

Request

bash
curl https://api.authon.dev/v1/auth/token/verify \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response

json
{
  "valid": true,
  "payload": {
    "sub": "user-uuid",
    "projectId": "project-uuid",
    "type": "access",
    "iat": 1711584000,
    "exp": 1711584900
  },
  "user": {
    "id": "user-uuid",
    "email": "user@example.com",
    "displayName": "Alan Kim",
    "avatarUrl": null,
    "emailVerified": true
  }
}
!

Always verify tokens server-side. Client-side token validation is insecure. Never issue service JWTs based on email alone — always verify the Authon token first.

getToken() Details

authon.getToken() returns the JWT issued by Authon. The token is signed with HS256 and expires after 15 minutes. The SDK automatically refreshes it when expired.

FieldValue
FormatJWT (HS256)
Expiry15 minutes
Auto-refreshYes (handled by SDK)
Payload.subUser UUID
Payload.projectIdProject UUID

Integration Checklist

Authon — Universelle Authentifizierungsplattform