Quick Start
Overview
Section titled “Overview”Every client app integrates with the auth service via the standard OAuth2 Authorization Code flow with PKCE. The flow is:
- User visits your app → no token → redirect to
auth.beshoy.ai - User authenticates (password, PIN, or magic link)
- Auth service redirects back with an authorization code
- Your app exchanges the code for tokens (server-side)
- Tokens stored in httpOnly cookies
Prerequisites
Section titled “Prerequisites”- A project registered in the auth service (via admin API or authdash)
- Your app’s redirect URI added to the project’s allowlist
- The project’s
signing_keyas yourJWT_SECRETenvironment variable - The project’s
client_secretfor token exchange
Environment Variables
Section titled “Environment Variables”JWT_SECRET=<project signing_key, 64-char hex>AUTH_CLIENT_SECRET=<project client_secret, 64-char hex>APP_URL=https://yourapp.beshoy.aiIntegration Steps
Section titled “Integration Steps”-
Generate PKCE parameters and redirect
When a user needs to authenticate, generate a code verifier, compute the challenge, encrypt state, and redirect:
import crypto from 'node:crypto';// Generate PKCEconst codeVerifier = crypto.randomBytes(32).toString('base64url');const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');// Encrypt state (code verifier + return URL)const state = await encryptState({ v: codeVerifier, r: returnTo });// Redirect to auth serviceconst authUrl = new URL('https://auth.beshoy.ai/oauth/authorize');authUrl.searchParams.set('client_id', 'proj_yourapp');authUrl.searchParams.set('redirect_uri', `${APP_URL}/api/auth/callback`);authUrl.searchParams.set('response_type', 'code');authUrl.searchParams.set('code_challenge', codeChallenge);authUrl.searchParams.set('code_challenge_method', 'S256');authUrl.searchParams.set('state', state);redirect(authUrl.toString()); -
Handle the callback
Create a callback route that exchanges the code for tokens:
// GET /api/auth/callbackexport async function GET(req: Request) {const url = new URL(req.url);const code = url.searchParams.get('code');const state = url.searchParams.get('state');// Decrypt state to get code verifierconst { v: codeVerifier, r: returnTo } = await decryptState(state);// Exchange code for tokensconst res = await fetch('https://auth.beshoy.ai/oauth/token', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({grant_type: 'authorization_code',code,code_verifier: codeVerifier,client_id: 'proj_yourapp',client_secret: process.env.AUTH_CLIENT_SECRET,redirect_uri: `${process.env.APP_URL}/api/auth/callback`,}),});const { access_token, refresh_token } = await res.json();// Set cookies and redirectsetAuthCookies(access_token, refresh_token);redirect(returnTo || '/');} -
Verify tokens on requests
Use the project’s signing key to verify JWTs locally:
import { jwtVerify } from 'jose';const secret = new TextEncoder().encode(process.env.JWT_SECRET);async function getUser(request: Request) {const token = getCookie(request, 'access_token');if (!token) return null;try {const { payload } = await jwtVerify(token, secret);return {userId: payload.sub,email: payload.email,role: payload.role,status: payload.status,};} catch {return null;}} -
Handle token refresh
When the access token expires, use the refresh token to get new ones:
const res = await fetch('https://auth.beshoy.ai/oauth/token', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({grant_type: 'refresh_token',refresh_token: currentRefreshToken,client_id: 'proj_yourapp',client_secret: process.env.AUTH_CLIENT_SECRET,}),});const { access_token, refresh_token } = await res.json();// Update cookies with new tokens
Token Lifetimes
Section titled “Token Lifetimes”| Token | Lifetime | Storage |
|---|---|---|
| Access token | 5 minutes | httpOnly cookie |
| Refresh token (user) | 7 days | httpOnly cookie |
| Refresh token (PIN) | 30 days | httpOnly cookie |