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.
FerrisKey implements the OpenID Connect protocol built on OAuth 2.0, providing:
FerrisKey currently supports four OAuth 2.0 grant types to accommodate different application architectures:
Best for: Web applications with backend servers
The most secure flow for applications that can securely store client secrets:
Best for: Backend services and API-to-API communication
Machine-to-machine authentication without user interaction:
Best for: Trusted first-party applications only
Direct username/password authentication:
Best for: Token renewal for all client types
Obtain new access tokens without re-authentication:
FerrisKey supports Time-based One-Time Passwords (TOTP) for enhanced security:
MFA integrates seamlessly with the authorization code flow:
FerrisKey exposes standard OpenID Connect endpoints per realm:
GET /realms/{realm_name}/protocol/openid-connect/auth
Purpose: Initiate the authorization code flow
Parameters:
client_id - Your application's client identifierresponse_type - Set to code for authorization code flowscope - Requested scopes (e.g., openid profile email)redirect_uri - Where to redirect after authenticationstate - 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
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
Parameters:
grant_type=authorization_codecode - Authorization code from auth endpointredirect_uri - Must match the URI from auth requestclient_id - Your application's client identifierclient_secret - Your application's client secretParameters:
grant_type=client_credentialsclient_id - Your application's client identifierclient_secret - Your application's client secretscope - Requested scopes (optional)Parameters:
grant_type=passwordusername - User's username or emailpassword - User's passwordclient_id - Your application's client identifierclient_secret - Your application's client secret (if required)scope - Requested scopes (optional)Parameters:
grant_type=refresh_tokenrefresh_token - The refresh token received previouslyclient_id - Your application's client identifierclient_secret - Your application's client secret (if required)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
});
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;
}
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;
}
FerrisKey issues standard JWT tokens:
Contains claims for API authorization:
sub - Subject (user ID)aud - Audience (your application)exp - Expiration timestampiat - Issued at timestampscope - Granted scopesContains user identity information:
sub - Subject (user ID)name - User's full nameemail - User's email addressemail_verified - Email verification statuspreferred_username - UsernameOpaque token for obtaining new access tokens:
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;
}
}
Track important authentication events: