Getting Started

Authentication

Learn how to configure and implement authentication in FerrisKey using OpenID Connect.

Authentication

FerrisKey provides OpenID Connect (OIDC) based authentication, enabling secure identity management for your applications. This guide covers the available authentication flows and how to implement them.

Overview

FerrisKey implements the OpenID Connect protocol built on OAuth 2.0, providing:

  • Standardized authentication following OIDC specifications
  • Multiple grant types for different application scenarios
  • Multi-factor authentication with TOTP support
  • Realm-based isolation for multi-tenant environments
FerrisKey's OIDC implementation ensures compatibility with standard OAuth 2.0/OIDC libraries across all major programming languages.

Available Grant Types

FerrisKey currently supports four OAuth 2.0 grant types to accommodate different application architectures:

Authorization Code Flow

Best for: Web applications with backend servers

The most secure flow for applications that can securely store client secrets:

  1. User initiates login - Redirect user to authorization endpoint
  2. User authentication - User authenticates with FerrisKey
  3. Authorization code - FerrisKey redirects back with authorization code
  4. Token exchange - Backend exchanges code for tokens using client secret
Recommended FlowThis is the most secure flow and should be used whenever possible, especially for applications with a backend component.

Client Credentials Flow

Best for: Backend services and API-to-API communication

Machine-to-machine authentication without user interaction:

  • Direct token request - Application authenticates using client ID and secret
  • Service tokens - Receive access tokens for API access
  • No user context - Tokens represent the application itself, not a user

Password Flow (Resource Owner Password Credentials)

Best for: Trusted first-party applications only

Direct username/password authentication:

  • Direct credential exchange - Send username/password directly to token endpoint
  • Immediate token response - Receive tokens without redirect flow
  • High trust requirement - Only use for applications you fully control
Security WarningThe password flow should only be used for highly trusted applications as it requires handling user credentials directly.

Refresh Token Flow

Best for: Token renewal for all client types

Obtain new access tokens without re-authentication:

  • Long-lived refresh tokens - Securely stored tokens for token renewal
  • Seamless experience - Users stay logged in without interruption
  • Enhanced security - Access tokens can have short lifespans

Multi-Factor Authentication (MFA)

FerrisKey supports Time-based One-Time Passwords (TOTP) for enhanced security:

TOTP Implementation

  • Compatible authenticator apps - Google Authenticator, Authy, Microsoft Authenticator
  • Standard RFC 6238 - Industry-standard TOTP algorithm
  • QR code enrollment - Easy setup for users
  • Backup codes - Recovery options when devices are unavailable

MFA Flow Integration

MFA integrates seamlessly with the authorization code flow:

  1. Initial authentication - User provides username/password
  2. MFA challenge - System prompts for TOTP code
  3. TOTP verification - User enters code from authenticator app
  4. Complete authentication - Authorization continues upon successful verification

OIDC Endpoints

FerrisKey exposes standard OpenID Connect endpoints per realm:

Authorization Endpoint

GET /realms/{realm_name}/protocol/openid-connect/auth

Purpose: Initiate the authorization code flow

Parameters:

  • client_id - Your application's client identifier
  • response_type - Set to code for authorization code flow
  • scope - Requested scopes (e.g., openid profile email)
  • redirect_uri - Where to redirect after authentication
  • state - CSRF protection parameter (recommended)

Example:

https://your-ferriskey.example.com/realms/my-app/protocol/openid-connect/auth
  ?client_id=my-web-app
  &response_type=code
  &scope=openid%20profile%20email
  &redirect_uri=https://myapp.com/callback
  &state=random-state-value

Token Endpoint

POST /realms/{realm_name}/protocol/openid-connect/token

Purpose: Exchange authorization codes for tokens or refresh existing tokens

Content-Type: application/x-www-form-urlencoded

Authorization Code Exchange

Parameters:

  • grant_type=authorization_code
  • code - Authorization code from auth endpoint
  • redirect_uri - Must match the URI from auth request
  • client_id - Your application's client identifier
  • client_secret - Your application's client secret

Client Credentials

Parameters:

  • grant_type=client_credentials
  • client_id - Your application's client identifier
  • client_secret - Your application's client secret
  • scope - Requested scopes (optional)

Password Grant

Parameters:

  • grant_type=password
  • username - User's username or email
  • password - User's password
  • client_id - Your application's client identifier
  • client_secret - Your application's client secret (if required)
  • scope - Requested scopes (optional)

Refresh Token

Parameters:

  • grant_type=refresh_token
  • refresh_token - The refresh token received previously
  • client_id - Your application's client identifier
  • client_secret - Your application's client secret (if required)

Implementation Examples

Web Application (Authorization Code)

Step 1: Redirect to Authorization

const authUrl = new URL('/realms/my-app/protocol/openid-connect/auth', 'https://ferriskey.example.com');
authUrl.searchParams.set('client_id', 'my-web-app');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
authUrl.searchParams.set('state', generateRandomState());

window.location.href = authUrl.toString();

Step 2: Handle Callback and Exchange Code

// Backend endpoint handling the callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Verify state parameter for CSRF protection
  if (!verifyState(state)) {
    return res.status(400).send('Invalid state parameter');
  }
  
  // Exchange code for tokens
  const tokenResponse = await fetch('https://ferriskey.example.com/realms/my-app/protocol/openid-connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      redirect_uri: 'https://myapp.com/callback',
      client_id: 'my-web-app',
      client_secret: process.env.CLIENT_SECRET
    })
  });
  
  const tokens = await tokenResponse.json();
  // Store tokens securely and redirect user
});

Service-to-Service (Client Credentials)

async function getServiceToken() {
  const response = await fetch('https://ferriskey.example.com/realms/my-service/protocol/openid-connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: 'my-service',
      client_secret: process.env.SERVICE_SECRET,
      scope: 'api:read api:write'
    })
  });
  
  const tokens = await response.json();
  return tokens.access_token;
}

Token Refresh

async function refreshAccessToken(refreshToken) {
  const response = await fetch('https://ferriskey.example.com/realms/my-app/protocol/openid-connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: 'my-web-app',
      client_secret: process.env.CLIENT_SECRET
    })
  });
  
  const tokens = await response.json();
  return tokens;
}

Token Structure

FerrisKey issues standard JWT tokens:

Access Token

Contains claims for API authorization:

  • sub - Subject (user ID)
  • aud - Audience (your application)
  • exp - Expiration timestamp
  • iat - Issued at timestamp
  • scope - Granted scopes

ID Token

Contains user identity information:

  • sub - Subject (user ID)
  • name - User's full name
  • email - User's email address
  • email_verified - Email verification status
  • preferred_username - Username

Refresh Token

Opaque token for obtaining new access tokens:

  • Long-lived (configurable expiration)
  • Single-use (new refresh token issued with each use)
  • Securely stored and transmitted

Best Practices

Security Considerations

  • Always use HTTPS in production environments
  • Validate state parameters to prevent CSRF attacks
  • Store client secrets securely - never expose in frontend code
  • Use short-lived access tokens with longer-lived refresh tokens
  • Implement proper token storage - secure HTTP-only cookies for web apps

Error Handling

Handle common error scenarios:

// Token endpoint error handling
if (!tokenResponse.ok) {
  const error = await tokenResponse.json();
  
  switch (error.error) {
    case 'invalid_grant':
      // Authorization code expired or invalid
      redirectToLogin();
      break;
    case 'invalid_client':
      // Client credentials are invalid
      console.error('Client configuration error');
      break;
    case 'invalid_request':
      // Missing or malformed parameters
      console.error('Request error:', error.error_description);
      break;
  }
}

Monitoring and Logging

Track important authentication events:

  • Successful authentications and token exchanges
  • Failed login attempts and their reasons
  • MFA enrollments and usage patterns
  • Token refresh patterns and failures
This authentication system provides a solid foundation for securing your applications. Start with the authorization code flow for web applications, and consider implementing MFA for enhanced security.