mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-06-22 04:11:55 +10:00
62f8270b3e
commit b2b0470a1d9267d042ec0ac66523c6635bf5b199
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 13:13:38 2026 +0200
chore: update .gitignore to include .vite-hooks and modify pnpm-lock.yaml for dependencies
commit d28fadb5cd8706c874e616102878b4a394ec84c1
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 13:08:04 2026 +0200
fix: remove timestamp conflict guard
commit c6998d9dbab19d09d3c8054feef1d2e4117555eb
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 12:11:51 2026 +0200
chore(release): v5.1.5
commit f33d168711804880e1f12e88d24290aae16cc258
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:58:35 2026 +0200
revert: compose.yml
commit d961e6535811a10c335525fb33a08d03e737278d
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:58:08 2026 +0200
refactor(agent): replace 'revert' terminology with 'restore' for clarity, resolves #3086
commit 17f351171be218e33f01c469d95e4164d4c8dc57
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:10:41 2026 +0200
refactor(pdf): simplify sidebar section filtering and update summary feature logic
commit d55179b9d76879e3204de185e8b53fadd0a107ed
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:53:37 2026 +0200
chore: update pnpm-lock.yaml and turbo.json
commit 7cade6980e1a04352536bd44ef773f338c4ef599
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:38:30 2026 +0200
fix(polyfill): add tested polyfill for Map Upsert methods
commit 26d175bb9c53d93225d1e907678445252c13d660
Merge: 1cf33dc6c 5b1297fa2
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:23:29 2026 +0200
Merge remote-tracking branch 'origin/main' into feat/explore-hono-orpc-migration
# Conflicts:
# packages/api/src/services/agent-url.ts
# packages/runtime-externals/package.json
commit 1cf33dc6c9d81735730ad656e16dab6501c6d6a1
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:22:12 2026 +0200
chore: preserve branch changes before main sync
commit b380a4b00fdbcdd81ff4f8ef72b330fd027ccda5
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Mon May 18 07:50:28 2026 +0200
chore: lot of fixes for monorepo migration
commit 8fcf0ec64e1c29572ebaff494338368bfcf75760
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 13:57:17 2026 +0200
chore: update knip version and refine web app routing with new SEO endpoints
commit 234e68086ff15610a93877354c98e2c020364533
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 12:10:06 2026 +0200
refactor(auth): update OAuth routes to include API prefix and remove unused schema endpoint
commit 91c84b9a8496b0ce21d71cae9f8b2a027638c9ac
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:54:29 2026 +0200
chore: update dependencies and enhance PWA metadata in web app
commit 150117d4a5a9dd6cd92c64891aad8cae90f6a7af
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:12:35 2026 +0200
docs: revise manifest-only pwa testing scope
commit 6b939a55661aec9dd8122b184e4b60a5c7325fb5
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:11:33 2026 +0200
docs: add manifest-only pwa design
commit 1422e1fc96c400948b273210a1067251087d15d4
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:05:04 2026 +0200
chore(dev): simplify server proxy config
commit bc2ff5a9f6fda41e6c40333c8f163aa23a6c5e48
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:04:50 2026 +0200
docs: add unsafe oauth redirect plan
commit 445359ebe9b96c1515bf1c4c3f73ba8a8448ec12
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:04:34 2026 +0200
feat(auth): add unsafe oauth redirect flag
commit 73fffdd24598e56b2793f7657919bc794835892e
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:55:02 2026 +0200
docs: design unsafe oauth redirect flag
commit c0066aa19c15fc8a4c8e5179ed49889c117519f4
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:22:04 2026 +0200
chore: update translation source paths
commit 9033da082418d252aafd6c2eed72f71f014be3d9
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:09:25 2026 +0200
refactor(arch): react spa + hono migration
commit 6f27936c11bda895977dc63ee550c3346d4ce24b
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 01:10:47 2026 +0200
docs: add docker nightly tagging design
commit ecc1fd9a88a0ee1dca2f1977dfc17f74527fe1da
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Thu May 14 20:05:44 2026 +0200
feat: migrate to hono spa server
353 lines
13 KiB
Plaintext
353 lines
13 KiB
Plaintext
---
|
|
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
|
|
|
|
<Info>
|
|
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.
|
|
</Info>
|
|
|
|
## 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:
|
|
|
|
<Tabs>
|
|
<Tab title="Option A: OIDC Discovery (Recommended)">
|
|
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`
|
|
|
|
</Tab>
|
|
|
|
<Tab title="Option B: Manual URLs">
|
|
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 |
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
### 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
|
|
```
|
|
|
|
<Warning>
|
|
Make sure the callback URL exactly matches what you configure in your OAuth provider. A mismatch will cause
|
|
authentication to fail.
|
|
</Warning>
|
|
|
|
<Info>
|
|
Built-in providers (Google, GitHub, LinkedIn) use callback URLs in this format: `{APP_URL}/api/auth/callback/
|
|
{provider}` (for example `.../google`, `.../github`, `.../linkedin`).
|
|
</Info>
|
|
|
|
## 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` |
|
|
|
|
<Info>
|
|
The OAuth provider **must** return an email address. If no email is provided, authentication will fail with an error.
|
|
</Info>
|
|
|
|
## Provider-Specific Setup
|
|
|
|
### Authentik
|
|
|
|
<Steps>
|
|
<Step title="Create an OAuth2/OpenID Provider">
|
|
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`
|
|
|
|
</Step>
|
|
|
|
<Step title="Create an Application">
|
|
Navigate to **Applications → Applications** and create a new application:
|
|
|
|
- **Name**: Reactive Resume
|
|
- **Slug**: `reactive-resume`
|
|
- **Provider**: Select the provider you just created
|
|
|
|
</Step>
|
|
|
|
<Step title="Copy credentials">From the provider settings, copy the **Client ID** and **Client Secret**.</Step>
|
|
|
|
<Step title="Configure environment variables">
|
|
```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"
|
|
```
|
|
</Step>
|
|
</Steps>
|
|
|
|
### Authelia
|
|
|
|
<Steps>
|
|
<Step title="Configure an OIDC client">
|
|
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
|
|
```
|
|
|
|
<Info>
|
|
Generate the hashed secret using: `authelia crypto hash generate pbkdf2 --variant sha512`
|
|
</Info>
|
|
|
|
</Step>
|
|
|
|
<Step title="Configure environment variables">
|
|
```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"
|
|
```
|
|
|
|
<Warning>
|
|
Use the **plain text** secret in Reactive Resume's environment, not the hashed version used in Authelia's configuration.
|
|
</Warning>
|
|
|
|
</Step>
|
|
</Steps>
|
|
|
|
### Keycloak
|
|
|
|
<Steps>
|
|
<Step title="Create a client">
|
|
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**
|
|
|
|
</Step>
|
|
|
|
<Step title="Configure redirect URI">
|
|
In the client settings, add the redirect URI:
|
|
|
|
- **Valid redirect URIs**: `https://resume.example.com/api/auth/oauth2/callback/custom`
|
|
|
|
</Step>
|
|
|
|
<Step title="Copy credentials">Go to the **Credentials** tab and copy the **Client secret**.</Step>
|
|
|
|
<Step title="Configure environment variables">
|
|
```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"
|
|
```
|
|
</Step>
|
|
</Steps>
|
|
|
|
### 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
|
|
|
|
<AccordionGroup>
|
|
<Accordion title="'OAuth Provider did not return an email address' error">
|
|
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
|
|
</Accordion>
|
|
|
|
<Accordion title="Redirect URI mismatch error">
|
|
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
|
|
</Accordion>
|
|
|
|
<Accordion title="Session/cookie issues after successful OAuth login">
|
|
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.
|
|
</Accordion>
|
|
|
|
<Accordion title="Custom OAuth button not appearing">
|
|
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.
|
|
|
|
</Accordion>
|
|
|
|
<Accordion title="CORS or network errors during authentication">
|
|
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
|
|
</Accordion>
|
|
|
|
<Accordion title="Dynamic client redirect URI is rejected">
|
|
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.
|
|
</Accordion>
|
|
|
|
<Accordion title="User profile data is missing or incorrect">
|
|
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.
|
|
|
|
</Accordion>
|
|
</AccordionGroup>
|
|
|
|
## Security Considerations
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Use HTTPS" icon="lock">
|
|
Always use HTTPS for both your Reactive Resume instance and OAuth provider in production. OAuth tokens should never
|
|
be transmitted over unencrypted connections.
|
|
</Card>
|
|
<Card title="Protect secrets" icon="key">
|
|
Never commit `OAUTH_CLIENT_SECRET` to version control. Use environment variables or a secrets manager.
|
|
</Card>
|
|
<Card title="Verify redirect URIs" icon="shield-check">
|
|
Configure your OAuth provider to only allow the exact redirect URI. Avoid wildcards in redirect URI configurations.
|
|
</Card>
|
|
<Card title="Protect auth internals" icon="shield">
|
|
Keep `AUTH_SECRET` and `BETTER_AUTH_API_KEY` private. Rotating `AUTH_SECRET` may invalidate active sessions.
|
|
</Card>
|
|
<Card title="Review scopes" icon="list-check">
|
|
Only request the scopes you need. The default (`openid profile email`) is sufficient for Reactive Resume.
|
|
</Card>
|
|
</CardGroup>
|