diff --git a/docs/docs.json b/docs/docs.json index 7833cb168..c90937a71 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -67,7 +67,7 @@ }, { "group": "Self-Hosting", - "pages": ["self-hosting/docker", "self-hosting/examples", "self-hosting/migration"] + "pages": ["self-hosting/docker", "self-hosting/examples", "self-hosting/sso", "self-hosting/migration"] }, { "group": "Contributing", diff --git a/docs/self-hosting/sso.mdx b/docs/self-hosting/sso.mdx new file mode 100644 index 000000000..cfdab4a07 --- /dev/null +++ b/docs/self-hosting/sso.mdx @@ -0,0 +1,321 @@ +--- +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. + + +## 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="" + +# 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 + + + + 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 + + + + 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. + + + Only request the scopes you need. The default (`openid profile email`) is sufficient for Reactive Resume. + +