Skip to main content

Authentication

Unchained Engine supports multiple authentication patterns to accommodate different user flows and integration requirements.

Authentication Strategies

StrategyUse Case
GuestAnonymous browsing and checkout
Email/PasswordTraditional user registration
WebAuthnPasswordless authentication
OIDCExternal identity providers (Google, Keycloak, etc.)
API TokenMachine-to-machine authentication

Anonymous vs Guest Users

Unchained distinguishes between anonymous visitors and guest users:

TypeCan BrowseCan Add to CartCan Checkout
AnonymousYesNoNo
GuestYesYesYes
RegisteredYesYesYes

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

  1. Get registration options (returns JSON with challenge, rp, user, pubKeyCredParams, etc.):
mutation GetCredentialCreationOptions {
createWebAuthnCredentialCreationOptions(username: "user@example.com")
}
  1. Create credential with browser WebAuthn API using the returned options:
const credential = await navigator.credentials.create({
publicKey: creationOptions
});
  1. Store the credential:
mutation AddWebAuthnCredentials($credentials: JSON!) {
addWebAuthnCredentials(credentials: $credentials) {
_id
webAuthnCredentials {
_id
}
}
}

Authentication Flow

  1. Get authentication options (returns JSON with challenge, rpId, allowCredentials, etc.):
mutation GetCredentialRequestOptions {
createWebAuthnCredentialRequestOptions(username: "user@example.com")
}
  1. Authenticate with browser WebAuthn API:
const credential = await navigator.credentials.get({
publicKey: requestOptions
});
  1. 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:

  • Google
  • 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:

  1. Get authorization URL: Custom query that returns the provider's OAuth URL
  2. User redirected to provider: User authenticates with the identity provider
  3. 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

RoleDescription
adminFull access to all operations
userAuthenticated 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;
},
},
},
});