This Get Started documents our public auth API for your organization's users. AuthFlow™ provides 20 total /auth routes: 7 session routes for authentication and user management, 3 passwordManagement routes for password resets, 2 emailVerification routes for email verification, and 8 twoFactor routes for two-factor authentication. The downloadable Insomnia collection below contains all endpoint definitions, parameters, and example requests matching the exact API contracts.
Organization owners call AuthFlow™ from server-rendered code only. Your browser applications should store secrets as server-side environment variables, for calling the AuthFlow™ API. This is because: unlike the majority of authentication services, AuthFlow™ is designed to be used server-to-server, for general NIST-standards compliance.
The organization owner (your) API key is required for core session-management and other routes.
The fooBar token is a short-lived anti-abuse token and should be fetched immediately before each sign-up or login attempt, such that it is always fresh and valid.
If login returns requires2FA--meaning the user has enabled their 2FA--then the user must complete one of the documented /auth/2fa verification exchanges to obtain a session token.
Use the exact collection your implementation team can import into Insomnia.
The collection includes:
This is the recommended organization owner workflow for wiring your organization to its authentication via AuthFlow™.
Public API origin.
Owner dashboard API key. Stored server-side only.
Role/RBAC value you assign to the auth user.
This is the recommended organization owner workflow for wiring your organization to its authentication via AuthFlow™.
Your browser code should never store AuthFlow™ API secrets, such as the owner API key.
These seven routes match authProduct.session from the backend root endpoint.
Generate a short-lived anti-abuse token before sign-up or login.
identityqueryrequiredEmail address for the auth user.
api_keyqueryrequiredOrganization API key from the AuthFlow™ owner dashboard.
rolequeryoptionalRBAC role to encode into the temporary token. Defaults to user.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM0NDg0NX0.Rm9vQmFyU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93"
}curl --get "$AUTHFLOW_BASE_URL/auth/fooBar" \ --data-urlencode "identity=user@example.com" \ --data-urlencode "api_key=$AUTHFLOW_API_KEY" \ --data-urlencode "role=detective"
This token expires in 45 seconds.
Fetch a fresh fooBar anti-abuse token immediately before sign-up or login.
Create a new authentication user inside the owner organization.
foobarqueryrequiredTemporary anti-abuse token returned by /auth/fooBar.
emailqueryrequiredUser email address.
passwordqueryrequiredPassword matching ^(?:(?=(.*[A-Z]))|(?:.*[a-z]))(?=(.*[0-9]))(?=(.*[!@#$%^&*-])).{8,32}$.
fullnamequeryrequiredFull name for the auth user.
rolequeryrequiredRBAC role for the user inside your application.
api_keyqueryrequiredOrganization API key.
disclaimedqueryrequiredBoolean-like value stored with the user record. The collection uses true.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM4ODAwMH0.U2Vzc2lvblRva2VuU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93"
}curl --get "$AUTHFLOW_BASE_URL/auth/signUp" \ --data-urlencode "foobar=$FOOBAR_TOKEN" \ --data-urlencode "api_key=$AUTHFLOW_API_KEY" \ --data-urlencode "role=customer" \ --data-urlencode "email=user@example.com" \ --data-urlencode "password=TestPass123!" \ --data-urlencode "fullname=John Smith" \ --data-urlencode "disclaimed=true"
Successful sign-up returns a session token immediately.
If the same email and role already exist in your organization, the API returns a 409 payload.
Authenticate an auth user and return either a session token or a 2FA challenge.
foobarqueryrequiredTemporary anti-abuse token returned by /auth/fooBar.
emailqueryrequiredUser email address.
passwordqueryrequiredUser password.
api_keyqueryrequiredOrganization API key.
rolequeryrequiredUser role for RBAC login.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM4ODAwMH0.U2Vzc2lvblRva2VuU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93"
}
or
{
"requires2FA": true,
"tempToken": "d2e6c30aa5f74af4a81f98e8830a6e49e9bb2f31e1a7406a8d2f2eb3ef3028a1",
"email": "user@example.com",
"org_id": "org-uuid",
"role": "detective"
}curl --get "$AUTHFLOW_BASE_URL/auth/login" \ --data-urlencode "foobar=$FOOBAR_TOKEN" \ --data-urlencode "email=user@example.com" \ --data-urlencode "password=$USER_PASSWORD" \ --data-urlencode "api_key=$AUTHFLOW_API_KEY" \ --data-urlencode "role=detective"
When 2FA is enabled, login returns tempToken instead of token.
tempToken is then exchanged through one of the /auth/2fa verification endpoints.
Validate a session token for an auth user.
sessionTokenqueryrequiredRS512 JWT session token.
emailqueryrequiredUser email address.
HTTP 200 when valid HTTP 401 when invalid or expired
curl --get "$AUTHFLOW_BASE_URL/auth/validate" \ --data-urlencode "sessionToken=$SESSION_TOKEN" \ --data-urlencode "email=user@example.com"
Return the current auth-user profile for a valid session.
sessionTokenqueryrequiredRS512 JWT session token.
{
"email": "user@example.com",
"fullname": "John Smith",
"role": "detective",
"org_id": "org-uuid"
}curl --get "$AUTHFLOW_BASE_URL/auth/me" \ --data-urlencode "sessionToken=$SESSION_TOKEN"
Log out the current auth user.
sessionTokenqueryrequiredRS512 JWT session token.
emailqueryrequiredUser email address.
{
"success": true
}curl --get "$AUTHFLOW_BASE_URL/auth/logout" \ --data-urlencode "sessionToken=$SESSION_TOKEN" \ --data-urlencode "email=user@example.com"
Delete a non-owner auth user in your organization (organization owner only).
Authorization: Bearer <sessionToken>headerrequiredSession token for the authenticated organization owner.
email_ownerbodyrequiredOwner email. Must match the token identity.
org_idbodyrequiredOrganization UUID. Must match the token organization.
user_emailbodyrequiredEmail of the user to delete.
{
"success": true,
"message": "User deleted successfully"
}
or HTTP 403 when attempting to delete owner or without owner permissions
or HTTP 404 when user does not existcurl -X POST "$AUTHFLOW_BASE_URL/auth/deleteUser" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SESSION_TOKEN" \
-d '{"email_owner":"owner@example.com","org_id":"'$AUTHFLOW_ORG_ID'","user_email":"member@example.com"}'This endpoint is owner-only and cannot delete the organization owner account.
The token identity and org_id must match the request body.
These three routes match authProduct.passwordManagement from the backend root endpoint.
Request a password-reset email for an auth user (enumeration-safe response).
emailbodyrequiredAuth-user email address.
api_keybodyrequiredOrganization API key.
rolebodyoptionalAuth-user role. Defaults to user.
{
"success": true,
"message": "If an account exists for this email, a reset link has been sent."
}curl -X POST "$AUTHFLOW_BASE_URL/auth/forgot-password" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","api_key":"'$AUTHFLOW_API_KEY'","role":"detective"}'Reset an auth-user password with a valid reset token.
tokenbodyrequiredPassword-reset token from the email link.
newPasswordbodyrequiredNew password matching ^(?:(?=(.*[A-Z]))|(?:.*[a-z]))(?=(.*[0-9]))(?=(.*[!@#$%^&*-])).{8,32}$.
{
"success": true
}curl -X POST "$AUTHFLOW_BASE_URL/auth/reset-password" \
-H "Content-Type: application/json" \
-d '{"token":"'$AUTH_RESET_TOKEN'","newPassword":"NewPass123!"}'Change the authenticated auth-user password.
sessionTokenqueryrequiredCurrent RS512 JWT session token.
emailqueryrequiredEmail encoded in the session token.
oldPasswordqueryrequiredCurrent password for confirmation.
newPasswordqueryrequiredReplacement password matching ^(?:(?=(.*[A-Z]))|(?:.*[a-z]))(?=(.*[0-9]))(?=(.*[!@#$%^&*-])).{8,32}$.
HTTP 200 on success
curl --get "$AUTHFLOW_BASE_URL/auth/changePassword" \ --data-urlencode "sessionToken=$SESSION_TOKEN" \ --data-urlencode "email=user@example.com" \ --data-urlencode "oldPassword=$OLD_PASSWORD" \ --data-urlencode "newPassword=NewPass123!"
These two routes match authProduct.emailVerification from the backend root endpoint.
Verify an auth-user email using the token delivered by AuthFlow™ email.
tokenqueryrequiredVerification token from the email verification link.
HTML verification page HTTP 200 when verified HTTP 401 for invalid or expired token
curl --get "$AUTHFLOW_BASE_URL/auth/verify-email" \ --data-urlencode "token=$VERIFY_EMAIL_TOKEN"
This endpoint is usually opened by clicking the verification link in email.
AuthFlow™ also supports frontend-hosted verification via /verify-email that proxies this check.
Resend the auth-user verification email when the account is not yet verified.
foobarqueryrequiredTemporary token returned by /auth/fooBar.
emailqueryrequiredAuth-user email address.
api_keyqueryrequiredOrganization API key.
rolequeryoptionalAuth-user role. Defaults to user.
{
"statusCode": 200,
"message": "Verification email sent."
}curl --get "$AUTHFLOW_BASE_URL/auth/resend-verify-email" \ --data-urlencode "foobar=$FOOBAR_TOKEN" \ --data-urlencode "email=user@example.com" \ --data-urlencode "api_key=$AUTHFLOW_API_KEY" \ --data-urlencode "role=detective"
These eight routes match authProduct.twoFactor from the backend root endpoint.
Generate a TOTP secret and QR code for an auth user.
sessionTokenbodyrequiredCurrent session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
{
"secret": "BASE32SECRET",
"qrCode": "data:image/png;base64,...",
"otpauth_url": "otpauth://totp/..."
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/enable" \
-H "Content-Type: application/json" \
-d '{"sessionToken":"'$SESSION_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective"}'Verify the initial TOTP code and enable 2FA.
sessionTokenbodyrequiredCurrent session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
totpCodebodyrequiredSix-digit code from the authenticator app.
secretbodyrequiredTOTP secret returned by /auth/2fa/enable.
{
"success": true,
"recoveryCodes": ["AB12CD34", "..."]
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/verify-setup" \
-H "Content-Type: application/json" \
-d '{"sessionToken":"'$SESSION_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","totpCode":"123456","secret":"'$TOTP_SECRET'"}'Disable TOTP 2FA for an auth user.
sessionTokenbodyrequiredCurrent session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
passwordbodyrequiredCurrent password.
{
"success": true
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/disable" \
-H "Content-Type: application/json" \
-d '{"sessionToken":"'$SESSION_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","password":"'$USER_PASSWORD'"}'Exchange a login tempToken and TOTP code for a session token.
tempTokenbodyrequiredtempToken returned by /auth/login when 2FA is enabled.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
totpCodebodyrequiredSix-digit authenticator code.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM4ODAwMH0.U2Vzc2lvblRva2VuU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93"
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/verify-2fa" \
-H "Content-Type: application/json" \
-d '{"tempToken":"'$TEMP_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","totpCode":"123456"}'Send a backup email OTP using a login tempToken.
tempTokenbodyrequiredtempToken returned by /auth/login.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
{
"success": true,
"message": "OTP sent to your email"
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/backup/email" \
-H "Content-Type: application/json" \
-d '{"tempToken":"'$TEMP_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective"}'Exchange an emailed OTP for a session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
emailOTPbodyrequiredSix-digit code delivered by email.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM4ODAwMH0.U2Vzc2lvblRva2VuU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93"
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/verify-email-otp" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","emailOTP":"123456"}'Regenerate recovery codes for an auth user.
sessionTokenbodyrequiredCurrent session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
passwordbodyrequiredCurrent password.
{
"recoveryCodes": ["AB12CD34", "..."]
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/recovery-codes" \
-H "Content-Type: application/json" \
-d '{"sessionToken":"'$SESSION_TOKEN'","email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","password":"'$USER_PASSWORD'"}'Exchange a recovery code for a session token.
emailbodyrequiredAuth-user email.
org_idbodyrequiredOrganization UUID.
rolebodyrequiredAuth-user role.
recoveryCodebodyrequiredOne of the 8-character recovery codes previously returned.
{
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJvcmdfaWQiOiJvcmctdXVpZCIsInJvbGUiOiJkZXRlY3RpdmUiLCJpYXQiOjE3NzUzNDQ4MDAsImV4cCI6MTc3NTM4ODAwMH0.U2Vzc2lvblRva2VuU2lnbmF0dXJlRXhhbXBsZVN0cmluZ0ZvckF1dGhGbG93",
"remainingCodes": 9
}curl -X POST "$AUTHFLOW_BASE_URL/auth/2fa/verify-recovery" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","org_id":"'$AUTHFLOW_ORG_ID'","role":"detective","recoveryCode":"AB12CD34"}'// lib/auth.ts using fetch with React statefulness
import { UserRole } from './types'
import { updateState } from './store'
// Cookie helpers — use Secure + SameSite=Strict for security.
function setCookie(name: string, value: string, days = 7): void {
if (typeof document === 'undefined') return
const expires = new Date(Date.now() + days * 864e5).toUTCString()
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Strict; Secure`
}
function getCookie(name: string): string | null {
if (typeof document === 'undefined') return null
const match = document.cookie.match(new RegExp('(?:^|;\s*)' + name + '=([^;]*)'))
return match ? decodeURIComponent(match[1]) : null
}
function deleteCookie(name: string): void {
if (typeof document === 'undefined') return
const isLocalhost = window.location.hostname === 'localhost'
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Strict; Secure`
}
export interface AuthUser {
email: string
fullname: string
org_id: string
role: UserRole
token: string
}
export interface AuthState {
user: AuthUser | null
isLoading: boolean
isAuthenticated: boolean
}
class AuthFlowClient {
private baseUrl: string
private apiKey: string
constructor() {
const isLocalhost = typeof window !== 'undefined' && window.location.hostname === 'localhost'
this.baseUrl = process.env.AUTHFLOW_API_URL
this.apiKey = process.env.AUTHFLOW_API_KEY
}
private async makeRequest(endpoint: string, params: Record<string, string>): Promise<{ data: any; status: number }> {
const url = new URL(`${this.baseUrl}${endpoint}`)
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
const response = await fetch(url.toString())
let data: any
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
data = await response.json()
} else {
// Handle non-JSON responses (like plain text error messages)
const text = await response.text()
data = { message: text, status: response.status }
}
return { data, status: response.status }
}
async getFooBarToken(email: string, role?: string): Promise<string> {
const { data: response } = await this.makeRequest('/auth/fooBar', { identity: email, api_key: this.apiKey, ...(role && { role }) })
return response.token
}
async signUp(email: string, password: string, fullname: string, role?: string): Promise<AuthUser> {
const foobar = await this.getFooBarToken(email, role)
const { data: response, status } = await this.makeRequest('/auth/signUp', {
foobar,
api_key: this.apiKey,
email,
password,
fullname,
disclaimed: 'true',
...(role && { role })
})
// Handle different error status codes
if (status === 400) {
if (response.message?.includes('Password does not meet complexity')) {
throw new Error('Password must be at least 8 characters long and contain uppercase, lowercase, and numbers')
} else if (response.message?.includes('Missing required fields')) {
throw new Error('Please fill in all required fields')
} else {
throw new Error('Invalid signup information. Please check your details and try again.')
}
} else if (status === 401) {
if (response.message?.includes('Invalid or expired token')) {
throw new Error('Signup session expired. Please refresh the page and try again.')
} else if (response.message?.includes('Invalid api_key')) {
throw new Error('Service temporarily unavailable. Please try again later.')
} else if (response.message?.includes('email verification')) {
throw new Error('Email verification is required. Please check your email for verification instructions.')
} else {
throw new Error('Unable to create account. Please try again later.')
}
} else if (status === 409) {
throw new Error('An account with this email and role already exists. Please sign in instead or choose a different role.')
} else if (status === 500) {
throw new Error('Server error. Please try again later.')
} else if (status !== 200) {
throw new Error('Unable to create account. Please try again.')
}
if (role === 'admin') {
const adminUserData = {
email: response?.email || email,
fullname: response?.fullname || fullname,
org_id: response?.org_id || '',
role: 'admin' as UserRole,
token: '',
}
// Update the app store with the admin user data
updateState({ user: adminUserData })
return adminUserData
}
// For non-admin roles, login after signup to get the token
return this.signIn(email, password, role)
}
async signIn(email: string, password: string, role?: string): Promise<AuthUser> {
const foobar = await this.getFooBarToken(email, role)
const { data: response, status } = await this.makeRequest('/auth/login', {
foobar,
email,
password,
api_key: this.apiKey,
...(role && { role })
})
// Handle different error status codes
if (status === 400) {
throw new Error('Please provide both email and password')
} else if (status === 401) {
if (response.message?.includes('Incorrect password')) {
throw new Error('Incorrect password. Please check your password and try again.')
} else if (response.message?.includes('Invalid api_key')) {
throw new Error('Service temporarily unavailable. Please try again later.')
} else {
throw new Error('Invalid login credentials. Please check your email and password.')
}
} else if (status === 404) {
throw new Error('There is no user with that signin info, please use the signup link.')
} else if (status === 500) {
throw new Error('Server error. Please try again later.')
} else if (status !== 200) {
throw new Error('Unable to sign in. Please try again.')
}
if (response.requires2FA) {
// TODO: 2FA implementation - this is a placeholder to indicate where 2FA logic would go
throw new Error('2FA required - not implemented yet')
}
// Get user details using /auth/me
const { data: userResponse, status: meStatus } = await this.makeRequest('/auth/me', {
sessionToken: response.token
})
if (meStatus !== 200) {
if (meStatus === 401) {
throw new Error('Session expired. Please sign in again.')
} else {
throw new Error('Unable to retrieve user information. Please try again.')
}
}
// Ensure we have valid user data
if (!userResponse || typeof userResponse !== 'object' || !userResponse.email) {
throw new Error('Invalid user data received. Please try again.')
}
const userData = {
email: userResponse.email,
fullname: userResponse.fullname,
org_id: userResponse.org_id,
role: userResponse.role as UserRole,
token: response.token
}
// Update the app store with the authenticated user data
updateState({ user: userData })
return userData
}
async signOut(token: string, email: string): Promise<void> {
await this.makeRequest('/auth/logout', {
sessionToken: token,
email
})
// Clear the user data from the app store
updateState({ user: null })
}
async validateToken(token: string, email: string): Promise<boolean> {
try {
await this.makeRequest('/auth/validate', {
sessionToken: token,
email
})
return true
} catch {
return false
}
}
async changePassword(sessionToken: string, email: string, oldPassword: string, newPassword: string): Promise<void> {
const { status } = await this.makeRequest('/auth/changePassword', {
sessionToken,
email,
oldPassword,
newPassword
})
// Handle different error status codes
if (status === 400) {
if (newPassword.length < 8 || !/(?=.*[a-z])(?=.*[A-Z])(?=.*d)/.test(newPassword)) {
throw new Error('Password must be at least 8 characters long and contain uppercase, lowercase, and numbers')
} else {
throw new Error('Missing required fields. Please provide email, old password, and new password.')
}
} else if (status === 401) {
throw new Error('Invalid session or incorrect old password. Please check your credentials and try again.')
} else if (status === 404) {
throw new Error('User not found. Please check your email address.')
} else if (status === 500) {
throw new Error('Server error. Please try again later.')
} else if (status !== 200) {
throw new Error('Unable to change password. Please try again.')
}
}
}
export const authFlowClient = new AuthFlowClient()
// Auth state management
let authState: AuthState = {
user: null,
isLoading: false,
isAuthenticated: false
}
const listeners: ((state: AuthState) => void)[] = []
export function subscribeToAuthState(listener: (state: AuthState) => void) {
listeners.push(listener)
return () => {
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
}
}
function notifyListeners() {
listeners.forEach(listener => listener(authState))
}
export function getAuthState(): AuthState {
return { ...authState }
}
export async function signUp(email: string, password: string, fullname: string, role?: string): Promise<AuthUser> {
authState.isLoading = true
notifyListeners()
try {
const user = await authFlowClient.signUp(email, password, fullname, role)
authState.user = user.token ? user : null
authState.isAuthenticated = Boolean(user.token)
authState.isLoading = false
notifyListeners()
if (user.token) {
setCookie('auth_user', JSON.stringify(user))
}
return user
} catch (error) {
authState.isLoading = false
notifyListeners()
throw error
}
}
export async function signIn(email: string, password: string, role?: string): Promise<AuthUser> {
authState.isLoading = true
notifyListeners()
try {
const user = await authFlowClient.signIn(email, password, role)
authState.user = user
authState.isAuthenticated = true
authState.isLoading = false
notifyListeners()
// Store in secure cookie
setCookie('auth_user', JSON.stringify(user))
return user
} catch (error) {
authState.isLoading = false
notifyListeners()
throw error
}
}
export async function signOut(): Promise<void> {
if (authState.user) {
try {
await authFlowClient.signOut(authState.user.token, authState.user.email)
} catch (error) {
console.error('Error signing out:', error)
}
}
authState.user = null
authState.isAuthenticated = false
authState.isLoading = false
notifyListeners()
// Clear auth cookie
deleteCookie('auth_user')
}
export async function initializeAuth(): Promise<void> {
const stored = getCookie('auth_user')
if (stored) {
try {
const user = JSON.parse(stored)
// Validate token
const isValid = await authFlowClient.validateToken(user.token, user.email)
if (isValid) {
authState.user = user
authState.isAuthenticated = true
notifyListeners()
// Update the app store with the restored user data
updateState({ user })
} else {
deleteCookie('auth_user')
// Clear the user data from the app store
updateState({ user: null })
}
} catch (error) {
console.error('Error initializing auth:', error)
deleteCookie('auth_user')
// Clear the user data from the app store
updateState({ user: null })
}
}
}Keep AUTHFLOW_API_KEY server-side and encrypted.
Prefer HTTP-only secure cookies for your application session.
Validate AuthFlow™ sessions before serving protected user data.
Handle 400, 401, 404, 409, and 500 responses explicitly in your integration.
You may also choose to proxy the AuthFlow backend, from your own ExpressJS backend. This is arguably the most-secure way to go about consuming AuthFlowTM in your application stacks.
The proxy forwards all AuthFlow /auth/* and /auth/2fa/* routes, keeps your organization API key server-side, and helps prevent secret exposure in browser code.
The proxy is Vercel-ready, supports local development immediately, and includes practical examples for sign-up, login, password flows, and 2FA verification.
I guide organization owners through the exact AuthFlow™ auth-user contract. Ask me about owner API key setup, /auth endpoints, 2FA flows, the Insomnia collection, frontend-backend boundaries, or Settings-page account deletion.