Authelia (OIDC)
This guide explains how to configure Authelia as an OpenID Connect (OIDC) identity provider for the Zero Kelvin application. Authelia is a self-hosted authentication and authorization server that provides single sign-on (SSO) and two-factor authentication.
Prerequisites
- A running Authelia instance (v4.37+)
- Access to your Authelia configuration file
- Your Authelia instance accessible via HTTPS
Step 1: Configure Authelia as an OIDC Provider
First, ensure your Authelia configuration has OIDC enabled. Add or update the identity_providers section in your Authelia configuration:
identity_providers:
oidc:
# Generate a secret using: openssl rand -hex 32
hmac_secret: your-hmac-secret-here
# You can generate these using:
# openssl genrsa -out private.pem 4096
# openssl rsa -in private.pem -outform PEM -pubout -out public.pem
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
... your private key ...
-----END RSA PRIVATE KEY-----
# Enforce minimum entropy for state parameter (default is 8)
enforce_pkce: public_clients_only
enable_pkce_plain_challenge: false
minimum_parameter_entropy: 8
clients:
- id: myapp
description: Example Application
# Generate a secret using: openssl rand -hex 32
secret: "$pbkdf2-sha512$310000$your-hashed-secret"
public: false
authorization_policy: two_factor # or 'one_factor'
redirect_uris:
- http://localhost:3000/api/auth/callback/authelia
- https://example.com/api/auth/callback/authelia
scopes:
- openid
- profile
- email
- groups
userinfo_signing_algorithm: none
consent_mode: implicit # or 'explicit' for user consent prompt
Generating the Client Secret
To generate a hashed client secret for Authelia:
# Generate a random secret
openssl rand -hex 32
# Use Authelia's built-in hasher (if available)
authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 32
Save the plaintext secret for your .env.local file and use the hashed version in Authelia's configuration.
Step 2: Update Auth.js Configuration
Add Authelia as an OIDC provider in your apps/web/src/auth.ts file:
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
import Facebook from "next-auth/providers/facebook";
import Apple from "next-auth/providers/apple";
import LinkedIn from "next-auth/providers/linkedin";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
}),
Facebook({
clientId: process.env.AUTH_FACEBOOK_ID,
clientSecret: process.env.AUTH_FACEBOOK_SECRET,
}),
Apple({
clientId: process.env.AUTH_APPLE_ID,
clientSecret: process.env.AUTH_APPLE_SECRET,
}),
LinkedIn({
clientId: process.env.AUTH_LINKEDIN_ID,
clientSecret: process.env.AUTH_LINKEDIN_SECRET,
}),
// Authelia OIDC Provider
{
id: "authelia",
name: "Authelia",
type: "oidc",
issuer: process.env.AUTH_AUTHELIA_ISSUER,
clientId: process.env.AUTH_AUTHELIA_ID,
clientSecret: process.env.AUTH_AUTHELIA_SECRET,
authorization: {
params: {
scope: "openid profile email groups",
},
},
checks: ["pkce", "state"], // Enable PKCE and state parameter validation
// Use ID token claims for profile data to ensure stable user IDs
// This is required for Pairwise Identifier Algorithm support
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name ?? profile.preferred_username ?? null,
email: profile.email ?? null,
image: null, // Authelia doesn't provide profile images by default
};
},
},
],
pages: {
signIn: "/auth/signin",
},
callbacks: {
async session({ session, token }) {
if (token?.sub) {
session.user.id = token.sub;
}
// Optionally include groups from Authelia
if (token?.groups) {
session.user.groups = token.groups;
}
return session;
},
async jwt({ token, profile }) {
// Store Authelia groups in the JWT token
if (profile?.groups) {
token.groups = profile.groups;
}
return token;
},
},
});
Step 3: Configure Environment Variables
Add the following to your .env.local file:
# Authelia OIDC Configuration
AUTH_AUTHELIA_ISSUER="https://auth.example.com"
AUTH_AUTHELIA_ID="zerokelvin"
AUTH_AUTHELIA_SECRET="your-plaintext-client-secret"
| Variable | Description |
|---|---|
AUTH_AUTHELIA_ISSUER | The base URL of your Authelia instance |
AUTH_AUTHELIA_ID | The client ID configured in Authelia (e.g., "zerokelvin") |
AUTH_AUTHELIA_SECRET | The plaintext client secret (not the hashed version) |
Step 4: Add Authelia to the Sign-In Page
Update apps/web/src/components/icons/provider-icons.tsx to add an Authelia icon:
export function AutheliaIcon(props: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
)
}
export const providerIcons = {
google: GoogleIcon,
facebook: FacebookIcon,
apple: AppleIcon,
linkedin: LinkedInIcon,
authelia: AutheliaIcon,
} as const
Then update the sign-in page to include Authelia:
const providers: Array<{
id: ProviderId;
name: string;
Icon: React.ComponentType<React.ComponentProps<"svg">>;
}> = [
{ id: "google", name: "Google", Icon: GoogleIcon },
{ id: "facebook", name: "Facebook", Icon: FacebookIcon },
{ id: "apple", name: "Apple", Icon: AppleIcon },
{ id: "linkedin", name: "LinkedIn", Icon: LinkedInIcon },
{ id: "authelia", name: "Authelia", Icon: AutheliaIcon },
];
Step 5: Test the Integration
- Ensure your Authelia instance is running and accessible
- Start the development server:
nx dev web
- Navigate to
http://localhost:3000 - Click Sign In
- Select Continue with Authelia
- You should be redirected to your Authelia login page
Using Groups for Authorization
Authelia can provide user groups in the OIDC response. You can use these for role-based access control:
import { auth } from "@/auth"
export default async function AdminPage() {
const session = await auth()
if (!session?.user?.groups?.includes("admins")) {
return <div>Access denied. Admin role required.</div>
}
return <div>Welcome, Admin!</div>
}
Advanced Configuration
Two-Factor Authentication
Configure Authelia to require 2FA for the Zero Kelvin application:
clients:
- id: zerokelvin
authorization_policy: two_factor
# ... rest of config
Custom Scopes
Request additional scopes from Authelia:
{
id: "authelia",
name: "Authelia",
type: "oidc",
issuer: process.env.AUTH_AUTHELIA_ISSUER,
clientId: process.env.AUTH_AUTHELIA_ID,
clientSecret: process.env.AUTH_AUTHELIA_SECRET,
authorization: {
params: {
scope: "openid profile email groups offline_access",
},
},
}
Session Timeout
Configure session duration in Authelia:
session:
expiration: 1h
inactivity: 5m
remember_me_duration: 1M
Production Checklist
Before deploying to production:
- Authelia is running behind HTTPS
- Client secret is securely generated and stored
- Redirect URIs include your production domain
- Authorization policy is appropriately set (one_factor or two_factor)
- Groups are properly configured for role-based access
- Session timeouts are configured appropriately
Troubleshooting
"State is missing or does not have enough characters"
This error occurs when Authelia's minimum_parameter_entropy requirement is not met by the OIDC client.
Solution:
- Ensure the Authelia provider configuration in
auth.tsincludeschecks: ["pkce", "state"] - Verify Authelia's OIDC configuration has
minimum_parameter_entropy: 8(default) - Make sure you're using Auth.js (NextAuth.js) v5, which properly generates secure state parameters
The application is configured to display detailed error messages when authentication fails, so you'll see a modal with the specific error details.
"Invalid client credentials"
- Verify the client ID matches exactly in both Authelia config and
.env.local - Ensure you're using the plaintext secret in
.env.local, not the hashed version - Check that the client is properly defined in Authelia's configuration
"Redirect URI mismatch"
- Ensure the redirect URI in Authelia includes the exact callback URL
- For development:
http://localhost:3000/api/auth/callback/authelia - For production:
https://yourdomain.com/api/auth/callback/authelia
"Discovery document not found"
- Verify the issuer URL is correct and accessible
- Check that Authelia's OIDC endpoint is enabled
- Ensure the URL doesn't have a trailing slash
"Groups not appearing in session"
- Verify the
groupsscope is requested in the authorization params - Check that the user belongs to groups in Authelia's user database
- Ensure the JWT callback is properly storing the groups