Customer Authentication
Customer authentication provides login, registration, and identity management for your end users. Foir supports password-based login, one-time passwords (OTP), and OAuth/OIDC providers.
Overview
Authentication Methods
| Method | Description | Use Case |
|---|---|---|
| Password | Email + password | Traditional login |
| OTP | One-time password sent via email | Passwordless login |
| OAuth/OIDC | External identity providers (Google, Auth0, etc.) | Social login, SSO |
All methods return JWT access and refresh tokens upon successful authentication. The access token is included in subsequent API requests to identify the customer.
JWT Tokens
- Access token: short-lived token included in the
Authorizationheader for authenticated requests - Refresh token: longer-lived token used to obtain new access tokens without re-authenticating
In the Admin
Setting Up Auth Providers
- Go to Settings > Authentication
- View the list of configured auth providers
- Click Add Provider to configure a new one:
- Key: unique identifier (e.g.,
google) - Name: display name (e.g., “Google Login”)
- Type: OAuth, OIDC, or other supported type
- Client ID / Secret: credentials from the provider
- Redirect URL: the callback URL for OAuth flows
- Enabled: toggle to activate or deactivate
- Priority: ordering for display in login UIs
- Key: unique identifier (e.g.,
- Save
JWT Configuration
Configure JWT settings in Settings > Authentication > JWT:
- Token expiration times
- Signing configuration
- Refresh token behavior
Via the CLI
# List configured auth providers
foir auth-providers list
# Get details for a specific provider
foir auth-providers get <id>
# Create a new auth provider
foir auth-providers create --data '{
"key": "google",
"name": "Google Login",
"type": "OAUTH",
"enabled": true,
"config": {
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"redirectUrl": "https://yoursite.com/auth/callback"
}
}'
# Update an auth provider
foir auth-providers update <id> --data '{"enabled": false}'
# Delete an auth provider
foir auth-providers delete <id>
# View current auth configuration
foir auth config
# Verify a customer token
foir auth verify-token <token>Via the API
Standard Login (Password)
mutation CustomerLogin($email: String!, $password: String!) {
customerLogin(email: $email, password: $password) {
success
accessToken
refreshToken
user {
id
email
userType
}
}
}Registration
mutation CustomerRegister($email: String!, $password: String!) {
customerRegister(email: $email, password: $password) {
success
accessToken
refreshToken
user {
id
email
}
emailVerificationRequired
}
}Logout
mutation CustomerLogout {
customerLogout {
success
message
}
}OTP Authentication
Request a one-time password, then use it to log in:
mutation RequestOTP($email: String!) {
customerRequestOTP(email: $email) {
success
expiresAt
message
}
}mutation LoginWithOTP($email: String!, $otp: String!) {
customerLoginOTP(email: $email, otp: $otp) {
success
accessToken
refreshToken
user {
id
email
}
}
}Password Reset
mutation RequestReset($email: String!) {
customerRequestPasswordReset(email: $email) {
success
message
}
}mutation ResetPassword($token: String!, $newPassword: String!) {
customerResetPassword(token: $token, newPassword: $newPassword) {
success
message
}
}Update Password (Authenticated)
mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {
customerUpdatePassword(
currentPassword: $currentPassword
newPassword: $newPassword
) {
success
message
}
}Token Refresh
mutation RefreshToken($refreshToken: String!) {
customerRefreshToken(refreshToken: $refreshToken) {
success
accessToken
refreshToken
}
}Current User
query CurrentUser {
currentUser {
id
email
emailVerified
status
userType
createdAt
}
}Auth Providers
List available providers for your login UI:
query ListProviders {
authProviders {
id
key
name
type
enabled
isDefault
priority
}
}OAuth Callback
After the user completes the OAuth flow, exchange the code:
mutation ProviderCallback($code: String!, $state: String!) {
customerProviderCallback(input: {
providerKey: "google"
code: $code
state: $state
}) {
accessToken
refreshToken
user {
id
email
}
isNewCustomer
}
}Auth Configuration
Query the project’s auth settings to build dynamic login forms:
query AuthConfig {
authConfig {
authMethods
publicRegistrationEnabled
passwordPolicy {
minLength
requireUppercase
requireLowercase
requireNumbers
requireSpecialChars
}
}
}Complete Login Flow Example
// 1. Check auth config to know which methods are available
const { data: config } = await client.query({ query: AUTH_CONFIG });
// 2. Login with email and password
const { data: login } = await client.mutate({
mutation: CUSTOMER_LOGIN,
variables: { email, password }
});
if (login.customerLogin.success) {
// 3. Store tokens
localStorage.setItem('accessToken', login.customerLogin.accessToken);
localStorage.setItem('refreshToken', login.customerLogin.refreshToken);
// 4. Use access token for authenticated requests
const { data: user } = await client.query({
query: CURRENT_USER,
context: {
headers: {
Authorization: `Bearer ${login.customerLogin.accessToken}`
}
}
});
}Using Access Tokens
Include the access token in subsequent requests:
curl -X POST https://api.foir.io/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: pk_live_..." \
-H "Authorization: Bearer eyJhbGc..." \
-d '{"query": "{ currentUser { id email } }"}'Config System
Auth providers can also be defined in foir.config.ts using defineAuthProvider. See the Configuration reference for details.
Project Email & Brand
Every project has a brand block that controls how customer-facing emails (welcome, password reset, OTP, email verification) look and where they link. These are project-level settings, not per-user.
| Field | Purpose |
|---|---|
displayName | Human-readable project name shown in emails (“Bob’s Country Bunker”) |
logoUrl | URL of the project logo embedded in email templates. Defaults to app.foir.dev/logo.png if unset. |
primaryColor | Brand colour applied to email buttons and accents (hex string, e.g. #2c4433) |
fromName | Display name on the From header (From: Bob's Country Bunker <noreply@…>) |
replyTo | Address customers reach when replying to platform emails |
supportEmail | Address shown to customers in error states (“Contact support@…”) |
customerPortalBaseURL | Origin of your storefront/portal — used as the base for links in emails ({customerPortalBaseURL}/reset-password?token=…). Required for password reset, OTP, and welcome emails to work. |
customerWelcomeEmailEnabled | Toggle: send a welcome email on customerRegister. Off by default. |
Configuring brand fields
In the admin: Settings → Project → Brand. Fill in display name, logo URL, primary colour, support address, and your customer portal URL.
If customerPortalBaseURL is unset, password-reset emails fail at send time with a clear “customer portal base URL is not configured” error — they can’t generate the reset link without it. Set it before turning on registration flows.
Welcome emails
When customerWelcomeEmailEnabled is true, the platform sends a welcome email with the project’s branding to every new customer registered via customerRegister. Toggle it off if your application sends its own welcome email and you don’t want a duplicate.
Email service
Outbound emails use a single EmailService driven by Resend. Email tags and templates are project-aware — every send pulls the project’s brand block to render the template. If you maintain multiple projects under one tenant, each project’s emails use its own brand independently.
Best Practices
- Always handle token refresh — implement automatic refresh when access tokens expire.
- Query auth config first — build login UIs dynamically based on enabled methods and password policies.
- Use OTP for passwordless flows — simpler for users and avoids password management.
- Verify tokens server-side — use
foir auth verify-tokenor thecurrentUserquery to validate sessions. - Set appropriate token lifetimes — balance security with user convenience.