Skip to main content

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"
VariableDescription
AUTH_AUTHELIA_ISSUERThe base URL of your Authelia instance
AUTH_AUTHELIA_IDThe client ID configured in Authelia (e.g., "zerokelvin")
AUTH_AUTHELIA_SECRETThe 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

  1. Ensure your Authelia instance is running and accessible
  2. Start the development server:
nx dev web
  1. Navigate to http://localhost:3000
  2. Click Sign In
  3. Select Continue with Authelia
  4. 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:

  1. Authelia is running behind HTTPS
  2. Client secret is securely generated and stored
  3. Redirect URIs include your production domain
  4. Authorization policy is appropriately set (one_factor or two_factor)
  5. Groups are properly configured for role-based access
  6. 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:

  1. Ensure the Authelia provider configuration in auth.ts includes checks: ["pkce", "state"]
  2. Verify Authelia's OIDC configuration has minimum_parameter_entropy: 8 (default)
  3. 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 groups scope 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

Additional Resources