Authentication
Unchained Engine supports multiple authentication patterns to accommodate different user flows and integration requirements.
Authentication Strategies
| Strategy | Use Case |
|---|---|
| Guest | Anonymous browsing and checkout |
| Email/Password | Traditional user registration |
| WebAuthn | Passwordless authentication |
| OIDC | External identity providers (Google, Keycloak, etc.) |
| API Token | Machine-to-machine authentication |
Anonymous vs Guest Users
Unchained distinguishes between anonymous visitors and guest users:
| Type | Can Browse | Can Add to Cart | Can Checkout |
|---|---|---|---|
| Anonymous | Yes | No | No |
| Guest | Yes | Yes | Yes |
| Registered | Yes | Yes | Yes |
Anonymous users can browse products and assortments without authentication. To perform state-changing operations (cart, checkout), a guest or registered user session is required.
Flow
Implementation
mutation LoginAsGuest {
loginAsGuest {
_id
tokenExpires
}
}
The session token is set as an HTTP-only cookie automatically. For subsequent requests, ensure cookies are sent with your requests.
mutation AddToCart {
addCartProduct(productId: "...", quantity: 1) {
_id
}
}
mutation Checkout {
checkoutCart {
_id
orderNumber
}
}
Guest to Registered Conversion
Guests can register without losing their cart or order history:
mutation CreateUser {
createUser(
email: "user@example.com"
password: "securepassword"
) {
_id
tokenExpires
}
}
The new account inherits:
- Current cart
- Order history
- Bookmarks
- Preferences
Email/Password Authentication
Traditional authentication with email and password.
Registration
mutation CreateUserWithProfile {
createUser(
email: "user@example.com"
password: "securepassword"
profile: {
displayName: "John Doe"
}
) {
_id
tokenExpires
user {
_id
primaryEmail {
address
}
profile {
displayName
}
}
}
}
Login
mutation Login {
loginWithPassword(
email: "user@example.com"
password: "securepassword"
) {
_id
tokenExpires
user {
_id
primaryEmail {
address
}
}
}
}
Password Reset
mutation ForgotPassword {
forgotPassword(email: "user@example.com") {
success
}
}
Reset with token (from email):
mutation ResetPassword {
resetPassword(
token: "reset-token-from-email"
newPassword: "newpassword"
) {
_id
tokenExpires
}
}
Change Password
mutation {
changePassword(
oldPassword: "currentpassword"
newPassword: "newpassword"
) {
success
}
}
WebAuthn (Passwordless)
Unchained Engine supports WebAuthn for passwordless authentication using biometrics or security keys.
Registration Flow
- Get registration options (returns JSON with challenge, rp, user, pubKeyCredParams, etc.):
mutation GetCredentialCreationOptions {
createWebAuthnCredentialCreationOptions(username: "user@example.com")
}
- Create credential with browser WebAuthn API using the returned options:
const credential = await navigator.credentials.create({
publicKey: creationOptions
});
- Store the credential:
mutation AddWebAuthnCredentials($credentials: JSON!) {
addWebAuthnCredentials(credentials: $credentials) {
_id
webAuthnCredentials {
_id
}
}
}
Authentication Flow
- Get authentication options (returns JSON with challenge, rpId, allowCredentials, etc.):
mutation GetCredentialRequestOptions {
createWebAuthnCredentialRequestOptions(username: "user@example.com")
}
- Authenticate with browser WebAuthn API:
const credential = await navigator.credentials.get({
publicKey: requestOptions
});
- Verify and login:
mutation LoginWithWebAuthn($credentials: JSON!) {
loginWithWebAuthn(webAuthnPublicKeyCredentials: $credentials) {
_id
tokenExpires
user {
_id
}
}
}
OIDC (External Identity Providers)
Integrate with external identity providers using OpenID Connect.
Supported Providers
Any OIDC-compliant provider:
- Apple
- Keycloak
- Zitadel
- Auth0
- Azure AD
- Custom providers
Configuration
OIDC integration is configured through the GraphQL context. See the OIDC Example for a complete implementation using startPlatform's context parameter to add custom authentication logic.
Login Flow
OIDC authentication is implemented via custom GraphQL resolvers. The flow typically involves:
- Get authorization URL: Custom query that returns the provider's OAuth URL
- User redirected to provider: User authenticates with the identity provider
- Exchange code for token: Custom mutation that validates the authorization code and creates a session
See the OIDC Example for a complete implementation showing how to add custom OIDC queries and mutations.
API Token Authentication
For server-to-server or automated access.
Using Tokens
Unchained users have a tokens field that stores authentication tokens. You can query a user's tokens:
query MyTokens {
me {
tokens {
_id
}
}
}
Invalidating Tokens
To invalidate a token:
mutation InvalidateToken {
invalidateToken(tokenId: "token-id") {
_id
}
}
Including Token in Requests
Include the token in the Authorization header:
Authorization: Bearer <token>
Session Management
Token Format
Unchained uses JWT tokens for authentication. Configure the token secret via environment variable:
UNCHAINED_TOKEN_SECRET=your-32-character-minimum-secret-here
Logout
mutation {
logout {
success
}
}
Current User
query CurrentUser {
me {
_id
primaryEmail {
address
}
username
profile {
displayName
address {
firstName
lastName
company
city
postalCode
countryCode
}
}
roles
cart {
_id
}
}
}
Role-Based Access Control
Unchained uses RBAC for authorization:
Built-in Roles
| Role | Description |
|---|---|
admin | Full access to all operations |
user | Authenticated user with standard permissions |
Checking Permissions
import { checkAction } from '@unchainedshop/roles';
// In a resolver
if (!checkAction(context, 'manageOrders')) {
throw new Error('Permission denied');
}
Custom Roles
import { Roles, Role } from '@unchainedshop/roles';
// Define custom role
const supportRole = new Role('support');
supportRole.allow('viewOrders', () => true);
supportRole.allow('updateOrderStatus', (context, { order }) => {
// Only pending orders
return order.status === 'PENDING';
});
Roles.registerRole(supportRole);
// Assign role to user
await modules.users.updateRoles(userId, ['support']);
Security Best Practices
1. Token Secret
Use a strong, unique secret:
UNCHAINED_TOKEN_SECRET=your-32-character-minimum-secret-here
2. HTTPS Only
Always use HTTPS in production. When connecting Unchained to your web server framework, configure secure cookies:
// When using Fastify
connect(fastify, platform, {
allowRemoteToLocalhostSecureCookies: process.env.NODE_ENV !== 'production',
});
3. Rate Limiting
Implement rate limiting for authentication endpoints:
import rateLimit from 'express-rate-limit';
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts',
});
app.use('/graphql', authLimiter);
4. Password Requirements
Configure password validation through the users module options:
await startPlatform({
options: {
users: {
validatePassword: async (password: string) => {
if (password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
throw new Error('Password must contain an uppercase letter');
}
if (!/[0-9]/.test(password)) {
throw new Error('Password must contain a number');
}
return true;
},
},
},
});
Related
- Users Module - User configuration options
- Admin UI - Admin UI overview