--- title: "Single Sign-On (SSO)" description: "A guide to setting up custom OAuth providers like Authentik, Authelia, Keycloak, or any OIDC-compliant identity provider for Single Sign-On (SSO) in your self-hosted instance." --- ## Overview Reactive Resume supports custom OAuth providers, allowing you to integrate with enterprise identity providers and self-hosted authentication solutions. This is particularly useful for organizations that want to: - Use a centralized identity provider (Authentik, Authelia, Keycloak, etc.) - Enforce Single Sign-On (SSO) across all internal applications - Integrate with existing LDAP/Active Directory infrastructure Custom OAuth is designed for **self-hosted instances**. If you're using the hosted version at [rxresu.me](https://rxresu.me), you can use the built-in Google and GitHub sign-in options. ## Environment Variables To enable a custom OAuth provider, you need to configure the following environment variables in your `.env` file: ### Required Variables | Variable | Description | | --------------------- | ------------------------------------------------- | | `OAUTH_CLIENT_ID` | The client ID provided by your OAuth provider | | `OAUTH_CLIENT_SECRET` | The client secret provided by your OAuth provider | ### Endpoint Configuration You must configure endpoints using **one** of these two methods: For OIDC-compliant providers (most modern identity providers), you only need to set the discovery URL: | Variable | Description | |----------|-------------| | `OAUTH_DISCOVERY_URL` | Your provider's `.well-known/openid-configuration` URL | The discovery URL automatically provides the authorization, token, and userinfo endpoints. **Examples:** - Authentik: `https://auth.example.com/application/o/reactive-resume/.well-known/openid-configuration` - Keycloak: `https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration` - Authelia: `https://auth.example.com/.well-known/openid-configuration` For providers that don't support OIDC discovery, you must set all three URLs: | Variable | Description | |----------|-------------| | `OAUTH_AUTHORIZATION_URL` | The URL where users are redirected to authorize | | `OAUTH_TOKEN_URL` | The URL to exchange authorization codes for tokens | | `OAUTH_USER_INFO_URL` | The URL to fetch user profile information | ### Optional Variables | Variable | Description | Default | | ------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------- | | `OAUTH_PROVIDER_NAME` | Display name shown on the sign-in button | `Custom OAuth` | | `OAUTH_SCOPES` | Space-separated list of OAuth scopes | `openid profile email` | ## Callback URL When configuring your OAuth provider, you'll need to set the **callback URL** (also called redirect URI). Use the following format: ``` {APP_URL}/api/auth/oauth2/callback/custom ``` For example, if your `APP_URL` is `https://resume.example.com`, the callback URL would be: ``` https://resume.example.com/api/auth/oauth2/callback/custom ``` Make sure the callback URL exactly matches what you configure in your OAuth provider. A mismatch will cause authentication to fail. Built-in providers (Google, GitHub, LinkedIn) use callback URLs in this format: `{APP_URL}/api/auth/callback/ {provider}` (for example `.../google`, `.../github`, `.../linkedin`). ## URL and Proxy Requirements - Set `APP_URL` to the exact public URL users access (prefer HTTPS in production). - Auth metadata, JWKS, and OAuth callback URLs are derived from `APP_URL`. - Behind a reverse proxy, forward `Host` and `X-Forwarded-Proto` correctly, or cookie/session behavior may break. - `trustedOrigins` are derived from `APP_URL`, so alternate domains are not automatically trusted. ## Profile Mapping Reactive Resume automatically maps user profile data from the OAuth provider. The following fields are used: | Reactive Resume Field | OAuth Profile Fields (in order of preference) | | --------------------- | --------------------------------------------- | | **Email** (required) | `email` | | **Name** | `name` → `preferred_username` → email prefix | | **Username** | `preferred_username` → email prefix | | **Avatar** | `image` → `picture` → `avatar_url` | The OAuth provider **must** return an email address. If no email is provided, authentication will fail with an error. ## Provider-Specific Setup ### Authentik In the Authentik admin interface, navigate to **Applications → Providers** and create a new **OAuth2/OpenID Provider**. - **Name**: Reactive Resume - **Authorization flow**: Use your preferred authorization flow - **Client type**: Confidential - **Redirect URIs**: `https://resume.example.com/api/auth/oauth2/callback/custom` Navigate to **Applications → Applications** and create a new application: - **Name**: Reactive Resume - **Slug**: `reactive-resume` - **Provider**: Select the provider you just created From the provider settings, copy the **Client ID** and **Client Secret**. ```bash .env OAUTH_PROVIDER_NAME="Authentik" OAUTH_CLIENT_ID="your-client-id" OAUTH_CLIENT_SECRET="your-client-secret" OAUTH_DISCOVERY_URL="https://auth.example.com/application/o/reactive-resume/.well-known/openid-configuration" ``` ### Authelia Add a client configuration to your Authelia `configuration.yml`: ```yaml identity_providers: oidc: clients: - client_id: reactive-resume client_name: Reactive Resume client_secret: "your-hashed-secret" # Use authelia hash-password to generate public: false authorization_policy: two_factor # or one_factor redirect_uris: - https://resume.example.com/api/auth/oauth2/callback/custom scopes: - openid - profile - email token_endpoint_auth_method: client_secret_post ``` Generate the hashed secret using: `authelia crypto hash generate pbkdf2 --variant sha512` ```bash .env OAUTH_PROVIDER_NAME="Authelia" OAUTH_CLIENT_ID="reactive-resume" OAUTH_CLIENT_SECRET="your-plain-secret" OAUTH_DISCOVERY_URL="https://auth.example.com/.well-known/openid-configuration" ``` Use the **plain text** secret in Reactive Resume's environment, not the hashed version used in Authelia's configuration. ### Keycloak In the Keycloak admin console: 1. Select your realm 2. Navigate to **Clients → Create client** 3. Set **Client ID** (e.g., `reactive-resume`) 4. Set **Client authentication** to **On** 5. Enable **Standard flow** In the client settings, add the redirect URI: - **Valid redirect URIs**: `https://resume.example.com/api/auth/oauth2/callback/custom` Go to the **Credentials** tab and copy the **Client secret**. ```bash .env OAUTH_PROVIDER_NAME="Keycloak" OAUTH_CLIENT_ID="reactive-resume" OAUTH_CLIENT_SECRET="your-client-secret" OAUTH_DISCOVERY_URL="https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration" ``` ### Generic OIDC Provider For any other OIDC-compliant provider: ```bash .env OAUTH_PROVIDER_NAME="My SSO" OAUTH_CLIENT_ID="your-client-id" OAUTH_CLIENT_SECRET="your-client-secret" OAUTH_DISCOVERY_URL="https://sso.example.com/.well-known/openid-configuration" ``` ### Non-OIDC Provider (Manual Configuration) For providers that don't support OIDC discovery: ```bash .env OAUTH_PROVIDER_NAME="Custom Provider" OAUTH_CLIENT_ID="your-client-id" OAUTH_CLIENT_SECRET="your-client-secret" OAUTH_AUTHORIZATION_URL="https://provider.example.com/oauth/authorize" OAUTH_TOKEN_URL="https://provider.example.com/oauth/token" OAUTH_USER_INFO_URL="https://provider.example.com/oauth/userinfo" OAUTH_SCOPES="openid profile email" ``` ## Complete Example Here's a complete `.env` snippet showing custom OAuth alongside other authentication options: ```bash .env # --- Authentication --- AUTH_SECRET="your-32-byte-hex-secret" # Built-in Social Auth (optional, can coexist with custom OAuth) # GOOGLE_CLIENT_ID="" # GOOGLE_CLIENT_SECRET="" # GITHUB_CLIENT_ID="" # GITHUB_CLIENT_SECRET="" # LINKEDIN_CLIENT_ID="" # LINKEDIN_CLIENT_SECRET="" # Custom OAuth Provider (e.g., Authentik) OAUTH_PROVIDER_NAME="Company SSO" OAUTH_CLIENT_ID="reactive-resume-client-id" OAUTH_CLIENT_SECRET="reactive-resume-client-secret" OAUTH_DISCOVERY_URL="https://auth.company.com/application/o/reactive-resume/.well-known/openid-configuration" # OAUTH_SCOPES="openid profile email" # Defaults to these scopes if not set ``` ## Troubleshooting Your OAuth provider must return an email address for user creation. Ensure: - The `email` scope is included in your scopes - Your provider is configured to release the email claim - The user has an email address set in the identity provider The callback URL configured in your OAuth provider must exactly match: ``` {APP_URL}/api/auth/oauth2/callback/custom ``` Common issues: - Trailing slash mismatch - HTTP vs HTTPS mismatch - Port number differences - Path case sensitivity Common cause: `APP_URL` does not match the real HTTPS public origin (for example, app is behind TLS but `APP_URL` is `http://...`). Fix: set `APP_URL` to the canonical HTTPS URL and restart the app. The custom OAuth option only appears if both `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are set, **and** either: - `OAUTH_DISCOVERY_URL` is set, **or** - All three manual URLs are set (`OAUTH_AUTHORIZATION_URL`, `OAUTH_TOKEN_URL`, `OAUTH_USER_INFO_URL`) Double-check your environment variables and restart the container. If running behind a reverse proxy: - Ensure `APP_URL` matches your public URL - Verify the proxy passes the correct headers (`X-Forwarded-Proto`, `X-Forwarded-Host`) - Check that your OAuth provider allows the redirect URI from your domain Dynamic OAuth client registration allows the app origin and local loopback callbacks by default. Trusted self-hosted deployments that need arbitrary redirect URIs can enable `FLAG_ALLOW_UNSAFE_OAUTH_REDIRECT_URI`, which permits any parseable redirect URI including custom schemes, private hosts, and non-loopback `http://` URLs. Do not enable it on public or multi-tenant deployments. The profile mapping depends on your provider returning standard claims: - `email` (required) - `name` or `preferred_username` for display name - `picture`, `image`, or `avatar_url` for avatar Check your provider's documentation to ensure these claims are included in the ID token or userinfo response. ## Security Considerations Always use HTTPS for both your Reactive Resume instance and OAuth provider in production. OAuth tokens should never be transmitted over unencrypted connections. Never commit `OAUTH_CLIENT_SECRET` to version control. Use environment variables or a secrets manager. Configure your OAuth provider to only allow the exact redirect URI. Avoid wildcards in redirect URI configurations. Keep `AUTH_SECRET` and `BETTER_AUTH_API_KEY` private. Rotating `AUTH_SECRET` may invalidate active sessions. Only request the scopes you need. The default (`openid profile email`) is sufficient for Reactive Resume.