fusionauth
npx skills add https://github.com/oscarangulo/skills --skill fusionauth
Agent 安装分布
Skill 文档
FusionAuth
Integrates authentication, authorization, and user management with FusionAuth using the official TypeScript SDK and REST APIs. Covers all auth flows, token lifecycle, webhooks, and multi-tenant deployments.
Workflow
1. Audit existing setup
Read:
.env/.env.localâ look forFUSIONAUTH_URL,FUSIONAUTH_API_KEY,FUSIONAUTH_APP_ID,FUSIONAUTH_TENANT_IDpackage.jsonâ confirm@fusionauth/typescript-clientis listed- Any existing auth files (e.g.,
lib/auth.ts,server/fusionauth.ts) â identify what flows are already implemented
If the SDK is not installed:
npm install @fusionauth/typescript-client
Required environment variables:
FUSIONAUTH_URL=https://your-instance.fusionauth.io # Self-hosted: http://localhost:9011
FUSIONAUTH_API_KEY=your-api-key # Created in FusionAuth Admin â API Keys
FUSIONAUTH_APP_ID=your-application-uuid # Application UUID from FusionAuth Admin
FUSIONAUTH_TENANT_ID=your-tenant-uuid # Only required for multi-tenant setups
2. Initialize the client
Use the template in assets/client-setup.ts as the starting point.
Single-tenant setup:
import { FusionAuthClient } from '@fusionauth/typescript-client'
export const fusionauth = new FusionAuthClient(
process.env.FUSIONAUTH_API_KEY!,
process.env.FUSIONAUTH_URL!
)
Multi-tenant setup (include tenant header on every request):
export const fusionauth = new FusionAuthClient(
process.env.FUSIONAUTH_API_KEY!,
process.env.FUSIONAUTH_URL!,
process.env.FUSIONAUTH_TENANT_ID // passed as X-FusionAuth-TenantId header
)
Why: Omitting the tenant ID in a multi-tenant deployment causes user lookups to fail silently or return users from the wrong tenant. Always pass it, even if you only have one tenant â it prevents ambiguity.
3. Standard login flow
Endpoint: POST /api/login
const result = await fusionauth.login({
loginId: 'user@example.com', // email, username, or phone
password: 'plaintext-password', // transmitted over TLS, hashed by FusionAuth
applicationId: process.env.FUSIONAUTH_APP_ID!,
noJWT: false, // set true for high-volume requests that don't need a JWT
})
switch (result.statusCode) {
case 200:
// Fully authenticated
const { token, refreshToken, user } = result.response
break
case 202:
// Authenticated but not registered to this application
// â Register user to app, then issue token
break
case 203:
// Password change required
// â Redirect to /change-password
break
case 212:
// Email or phone verification required
// â Show verification prompt
break
case 213:
// Registration verification required
// â Show registration verification prompt
break
case 242:
// MFA required â continue with MFA flow (see §4)
const { twoFactorId } = result.response
break
case 404:
// Invalid credentials â show generic "invalid email or password" (never reveal which is wrong)
break
}
See references/response-codes.md for the full response code reference.
4. MFA (Two-Factor Authentication) flow
When login returns 242, the response contains a twoFactorId (valid for ~5 minutes). Use it to complete authentication:
// Step 1: Initial login returns twoFactorId
const loginResult = await fusionauth.login({ loginId, password, applicationId })
// loginResult.statusCode === 242
const { twoFactorId } = loginResult.response
// Step 2: User enters code from authenticator app / email / SMS
const mfaResult = await fusionauth.twoFactorLogin({
twoFactorId,
code: '123456', // TOTP code, email code, or SMS code
applicationId: process.env.FUSIONAUTH_APP_ID!,
})
// mfaResult.statusCode === 200 â fully authenticated
const { token, refreshToken, user } = mfaResult.response
Enable MFA for a user:
// 1. Generate a TOTP secret
const secretResult = await fusionauth.generateTwoFactorSecret()
const { secret, secretBase32Encoded } = secretResult.response
// Show secretBase32Encoded as QR code to the user
// 2. User scans QR code and confirms with a code
await fusionauth.enableTwoFactor(userId, {
code: '123456', // confirmation code from authenticator app
method: 'authenticator', // 'authenticator' | 'email' | 'sms'
secret,
})
MFA methods by plan:
| Method | Plans | Notes |
|---|---|---|
authenticator (TOTP) |
All | Google Authenticator, Authy compatible |
email |
Starter+ | Code delivered via email |
sms |
Starter+ | Code delivered via SMS |
5. Passwordless login flow
Three-step flow â start â send â complete:
// Step 1: Generate a passwordless code
const startResult = await fusionauth.startPasswordlessLogin({
loginId: 'user@example.com',
applicationId: process.env.FUSIONAUTH_APP_ID!,
state: {},
})
const { code } = startResult.response // one-time code, expires ~5 min
// Step 2: Send the code to the user (optional if using ClickableLink method)
await fusionauth.sendPasswordlessCode({
code,
})
// Step 3: User submits the code â complete login
const loginResult = await fusionauth.passwordlessLogin({
code,
applicationId: process.env.FUSIONAUTH_APP_ID!,
})
const { token, refreshToken } = loginResult.response
6. WebAuthn / Passkeys
Registration ceremony (add a passkey to an existing account):
// Step 1: Get registration options from FusionAuth
const startResult = await fusionauth.startWebAuthnRegistration(userId, {
displayName: 'My Laptop',
name: 'user@example.com',
userVerificationRequirement: 'required',
workflow: 'general',
})
const { options } = startResult.response
// Step 2: Browser performs navigator.credentials.create(options)
// Step 3: Send credential back to FusionAuth
const completeResult = await fusionauth.completeWebAuthnRegistration({
credential: browserCredentialResponse,
})
Authentication ceremony:
// Step 1: Get assertion options
const assertResult = await fusionauth.startWebAuthnLogin({
loginId: 'user@example.com',
applicationId: process.env.FUSIONAUTH_APP_ID!,
workflow: 'general',
})
const { options } = assertResult.response
// Step 2: Browser performs navigator.credentials.get(options)
// Step 3: Verify credential
const loginResult = await fusionauth.completeWebAuthnLogin({
credential: browserAssertionResponse,
applicationId: process.env.FUSIONAUTH_APP_ID!,
})
const { token, refreshToken } = loginResult.response
7. User registration and management
Create a user and register them to an application in a single call:
const result = await fusionauth.register(undefined, {
user: {
email: 'user@example.com',
password: 'secure-password',
firstName: 'Ada',
lastName: 'Lovelace',
},
registration: {
applicationId: process.env.FUSIONAUTH_APP_ID!,
roles: ['user', 'admin'], // use role NAMES, not UUIDs
},
sendSetPasswordEmail: false,
skipVerification: false,
})
const { user, registration, token } = result.response
Update a user (partial patch):
await fusionauth.patchUser(userId, {
user: {
firstName: 'Ada',
data: { plan: 'pro' }, // store arbitrary key-value data here
},
})
Register an existing user to an application:
await fusionauth.register(userId, {
registration: {
applicationId: process.env.FUSIONAUTH_APP_ID!,
roles: ['user'],
},
})
Retrieve user by different identifiers:
// By userId
const { response: { user } } = await fusionauth.retrieveUser(userId)
// By email
const { response: { user } } = await fusionauth.retrieveUserByEmail('user@example.com')
// By login ID (email or username)
const { response: { user } } = await fusionauth.retrieveUserByLoginId('user@example.com')
8. JWT and token management
Refresh an access token:
const result = await fusionauth.exchangeRefreshTokenForAccessToken(refreshToken)
// result.statusCode === 200
const { token: newAccessToken, refreshToken: newRefreshToken } = result.response
Why refresh tokens may not be issued:
application.loginConfiguration.generateRefreshTokensdefaults tofalse. Enable it in FusionAuth Admin â Applications â [App] â OAuth â Refresh token grant.
Validate a JWT (server-side check):
// Via SDK
const result = await fusionauth.validateJWT(accessToken)
// result.statusCode === 200 â valid; 401 â expired or invalid
// Or call the API directly:
// GET /api/jwt/validate
// Authorization: Bearer <token>
Retrieve public keys for local verification:
// Fetch once and cache; re-fetch only on JWT kid mismatch
const keysResult = await fusionauth.retrieveJwtPublicKeys()
const { publicKeys } = keysResult.response
Revoke all refresh tokens for a user (force logout all sessions):
await fusionauth.revokeRefreshTokensByUserId(userId)
Revoke a single refresh token:
await fusionauth.revokeRefreshTokenByToken(refreshToken)
9. Webhooks
Create a webhook subscription:
await fusionauth.createWebhook({
webhook: {
url: 'https://your-app.com/webhooks/fusionauth',
eventsEnabled: {
'user.create': true,
'user.login.success': true,
'user.delete': true,
'jwt.refresh-token.revoke': true,
'user.password.breach': true, // Requires Enterprise plan
},
connectTimeout: 1000,
readTimeout: 2000,
global: true, // false â tenant-specific
signatureConfiguration: {
enabled: true, // Available in v1.48.0+
},
},
})
Handle incoming webhook events (see assets/webhook-handler.ts for a full Express handler):
// Verify signature before trusting payload
import * as crypto from 'crypto'
function verifyWebhookSignature(body: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(`sha256=${expected}`))
}
app.post('/webhooks/fusionauth', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-fusionauth-signature'] as string
if (!verifyWebhookSignature(req.body.toString(), signature, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body.toString())
// handle event.type
res.sendStatus(200)
})
See references/webhook-events.md for the full event catalogue.
10. OAuth2 / OIDC
PKCE flow for SPAs and server-side apps:
// 1. Redirect user to FusionAuth authorization endpoint
const authUrl = new URL(`${process.env.FUSIONAUTH_URL}/oauth2/authorize`)
authUrl.searchParams.set('client_id', process.env.FUSIONAUTH_APP_ID!)
authUrl.searchParams.set('redirect_uri', 'https://your-app.com/auth/callback')
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', 'openid offline_access')
authUrl.searchParams.set('code_challenge', pkceChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
// redirect(authUrl.toString())
// 2. After redirect, exchange code for tokens
const tokenResult = await fusionauth.exchangeOAuthCodeForAccessTokenUsingPKCE(
code,
'your-client-secret', // empty string for public clients
'https://your-app.com/auth/callback',
process.env.FUSIONAUTH_APP_ID!,
codeVerifier
)
const { access_token, refresh_token, id_token } = tokenResult.response
Logout (invalidate session):
const logoutUrl = new URL(`${process.env.FUSIONAUTH_URL}/oauth2/logout`)
logoutUrl.searchParams.set('client_id', process.env.FUSIONAUTH_APP_ID!)
logoutUrl.searchParams.set('post_logout_redirect_uri', 'https://your-app.com')
// redirect(logoutUrl.toString())
11. Multi-tenant patterns
When a single FusionAuth instance serves multiple independent tenants:
// Always include X-FusionAuth-TenantId in the client constructor or per-request
const tenantClient = new FusionAuthClient(
process.env.FUSIONAUTH_API_KEY!,
process.env.FUSIONAUTH_URL!,
tenantId // scopes ALL requests to this tenant
)
// Or dynamically per-request (create a new client per tenant):
function getClientForTenant(tenantId: string) {
return new FusionAuthClient(
process.env.FUSIONAUTH_API_KEY!,
process.env.FUSIONAUTH_URL!,
tenantId
)
}
Why: In multi-tenant mode, identical email addresses can exist in different tenants. Without the tenant ID header, FusionAuth may return users from the wrong tenant or return a 400/404 ambiguity error.
12. Identity providers (social login)
Identity providers are configured in FusionAuth Admin â the SDK handles the token exchange after the OAuth redirect:
// After user returns from Google/Apple/etc. with an authorization code:
const result = await fusionauth.identityProviderLogin({
applicationId: process.env.FUSIONAUTH_APP_ID!,
data: {
token: googleIdToken, // ID token from the identity provider
},
identityProviderId: 'google-identity-provider-uuid',
noJWT: false,
})
const { token, refreshToken, user } = result.response
Supported identity provider types (15 total):
- Social: Google, Apple, Facebook, Twitter/X, LinkedIn, GitHub, Twitch, Steam, Xbox, PlayStation, Epic Games
- Federated: SAML v2, OpenID Connect (OIDC), External JWT
- Directory: HYPR
References
references/api-cheatsheet.mdâ All FusionAuth API endpoints in one quick-scan table. Read this when you need an endpoint path or HTTP method quickly.references/response-codes.mdâ Full HTTP status code reference for the Login API. Read this when building conditional login flows or debugging unexpected response codes.references/gotchas.mdâ Common integration mistakes and how to fix them. Read this when something isn’t working or before starting a new integration.references/webhook-events.mdâ Full catalogue of 40+ webhook event types with payload shapes and plan requirements. Read this when configuring webhooks or building event handlers.assets/client-setup.tsâ Ready-to-use TypeScript client with wrapper functions for every major flow. Copy and customize for your project.assets/webhook-handler.tsâ Express.js webhook handler with signature verification and typed event routing. Copy for server-side webhook endpoints.