Single Sign-On (SSO)
Nitro Repo validates signed JWT/ID tokens from upstream SSO providers (Cloudflare Access, Okta, Auth0, Azure B2C, etc.) using JWKS. Tokens are verified per-provider, mapped to a Nitro user, optional Casbin roles are applied, and a Nitro Repo session cookie is issued.
Configuration
Configure the security.sso block inside cfg/nitro_repo.toml (or via Admin → System → Single Sign-On):
[security.sso]
enabled = true
login_path = "/api/user/sso/login"
login_button_text = "Sign in with SSO"
provider_login_url = "https://example.com/login" # optional
provider_redirect_param = "redirect" # optional
auto_create_users = true
role_claims = ["roles", "role", "groups"] # optional global role claim keys
[[security.sso.providers]]
name = "cloudflare"
issuer = "https://<team>.cloudflareaccess.com"
audience = "<your-aud-tag>"
jwks_url = "https://<team>.cloudflareaccess.com/cdn-cgi/access/certs"
token_source = { kind = "header", name = "Cf-Access-Jwt-Assertion" }
subject_claim = "sub" # optional, defaults to preferred_username/sub
email_claim = "email" # optional
display_name_claim = "name" # optional
role_claims = ["roles", "role"] # optional per-provider overridesKey points:
- Define one or more providers. Each token is verified against the provider’s JWKS using the
kidin the header and checked for matchingiss/aud. - Tokens can be sourced from headers or cookies; optional prefixes (e.g.,
Bearer) are stripped automatically. role_claims(global or per-provider) pull role values from string or string-array claims and apply them to Casbin before redirecting the user back to the UI.- Users are auto-provisioned when
auto_create_usersis true; otherwise they must exist beforehand.
OIDC / JWT Providers (JWKS)
Header-based SSO still works, but Nitro Repo can now validate signed ID tokens directly against a provider's JWKS endpoint. Add one or more providers under security.sso.providers:
[security.sso]
enabled = true
login_path = "/api/user/sso/login"
login_button_text = "Sign in with SSO"
[[security.sso.providers]]
name = "cloudflare"
issuer = "https://<team>.cloudflareaccess.com"
audience = "<your-aud-tag>"
jwks_url = "https://<team>.cloudflareaccess.com/cdn-cgi/access/certs"
token_source = { kind = "header", name = "Cf-Access-Jwt-Assertion" }
# Optional claim overrides
subject_claim = "sub"
email_claim = "email"
display_name_claim = "name"Each provider entry:
name: Friendly identifier for logs.issuer: Expectedissclaim.audience: Expectedaudclaim (often the OAuth/OIDC client ID).jwks_url: JWKS endpoint. If omitted Nitro Repo will try/.well-known/openid-configurationdiscovery on the issuer.token_source: Where to read the token (headerorcookie). For headers you can supply an optionalprefix(default"Bearer ").subject_claim/email_claim/display_name_claim: Optional claim keys when a provider deviates frompreferred_username,email, orname.
Keys fetched from JWKS are cached for one hour and refreshed automatically when they expire or a new kid shows up. If no provider yields a valid token, Nitro Repo falls back to the legacy header-based extraction above.
Runtime Configuration
Administrators can update these settings without restarting the server under Admin → System → Single Sign-On. Changes persist in the database and are visible immediately on the login screen.
Frontend Experience
When SSO is enabled, the login page shows a "Sign in with SSO" button above the traditional username/password form. Users bypass the password flow entirely once the SSO proxy authenticates them. The frontend app automatically handles redirect targets (e.g., deep links to repository pages) when returning from /api/user/sso/login.
Reverse Proxy Checklist
- Ensure the upstream SSO proxy terminates TLS and authenticates users before forwarding to Nitro Repo.
- Strip or overwrite the identity headers so untrusted clients cannot spoof them.
- Allow access to
/api/user/sso/login,/api/user/logout, and the SPA assets.
With these pieces in place, Nitro Repo delegates authentication to your enterprise IdP while retaining its existing session and authorization model.
OAuth2 / OIDC Login (Google & Microsoft)
In addition to trusting upstream SSO headers, Nitro Repo can act as an OAuth2 client and talk directly to Google or Microsoft Entra ID (Azure AD). The backend handles the full authorization code flow with PKCE, validates ID tokens, and optionally maps IdP groups/roles into Casbin RBAC policies.
Enabling OAuth2 in the Admin UI
- Sign in as an administrator and open Admin → System. The page now has a dedicated OAuth2 Providers card beneath the legacy header-based SSO settings.
- Populate the provider credentials (client ID/secret) and set the callback URL to
https://<your-domain>/api/user/oauth2/callback. - (Optional) Provide paths to a Casbin
model.confandpolicy.csv. Nitro Repo reloads these files whenever you save, allowing you to map IdP roles to application permissions. - Click Save. The server validates the configuration, writes it to the database, and updates the in-memory OAuth2 client. No restart is required.
Google Cloud Console Setup
- Create an OAuth client inside APIs & Services → Credentials.
- Choose Web application and add the Nitro Repo callback URL (
https://<your-domain>/api/user/oauth2/callback) under Authorized redirect URIs. - Copy the Client ID and Client Secret into the Nitro Repo OAuth2 settings.
- Enable the scopes you want to request. Nitro Repo defaults to
openid profile email, which is sufficient to retrieve the user’s identity. - (Optional) If you need Google Workspace group claims, enable the Admin SDK API and configure domain-wide delegation or use the Groups API in a webhook that populates Casbin policies.
Microsoft Entra ID Setup
- Open the Azure Portal and register an application under Entra ID → App registrations.
- Record the Application (client) ID and create a Client secret.
- Set a Redirect URI of type Web:
https://<your-domain>/api/user/oauth2/callback. - Decide which tenant scope to use. Nitro Repo accepts a specific tenant ID or
commonfor multi-tenant sign-in. - Grant the
openid,profile, andemailAPI permissions for the Microsoft Graph. Add theGroupMember.Read.Allpermission if you want group claims in the ID token. - Paste the client information into the Nitro Repo OAuth2 configuration and, if desired, provide the tenant ID and additional scopes.
Mapping Provider Roles to Casbin Policies
- The OAuth2 callback inspects the
rolesandgroupsclaims in the ID token. - Microsoft Entra can emit security groups or application roles. Google typically requires an external process to supply group membership; Nitro Repo falls back to
group:<email>when none are present. - Any roles discovered are synchronized with the configured Casbin model/policy. Nitro Repo assigns the authenticated email address as the Casbin subject and rewrites its group membership before each session.
- In the UI you can add Group to role mappings that translate provider group IDs into Nitro roles (for example mapping
Employee.ReadWrite.Allto theread/writeCasbin role). Mappings are applied in addition to the raw roles in the token, so you can keep both coarse and fine-grained assignments. - Nitro Repo exposes every role it finds in the current Casbin policy as auto-complete suggestions when you edit the mapping list. That list is generated directly from the policy stored in the database.
Casbin Model & Policy Editor
OAuth2 RBAC definitions now live alongside other application settings in PostgreSQL. The Admin → System screen exposes a pair of editors where you can update the Casbin model (INI syntax) and policy (CSV syntax). Saving the form writes the values back to the database, reloads the in-memory Enforcer, and updates the auto-complete suggestions immediately—no more copying files into containers.
If you prefer to start from the built-in defaults, the editor pre-populates them using the same templates that previously shipped as resources/rbac/model.conf and resources/rbac/policy.csv. Leave either textarea blank to revert to those defaults.
- In the UI you can add Group to role mappings that translate provider group IDs into Nitro roles (for example mapping
Employee.ReadWrite.Allto theread/writeCasbin role). Mappings are applied in addition to the raw roles in the token, so you can keep both coarse and fine-grained assignments.
Frontend Experience
Once configured, the login page displays provider-specific buttons that hit:
GET /api/user/oauth2/login/googleGET /api/user/oauth2/login/microsoft
Nitro Repo redirects the user to the provider’s consent screen, exchanges the authorization code on callback, issues a Nitro Repo session cookie, and finally forwards the user to the original redirect query parameter.
Example: Cloudflare One (One-Time PIN)
When Nitro Repo runs behind a Cloudflare Access application, Cloudflare authenticates users and adds identity headers to the proxied request. For the One-Time PIN IdP:
CF-Access-Authenticated-User-Emailcontains the user’s verified email address.CF-Access-Authenticated-User-Name(if present) contains a friendly display name.- Every authenticated request also carries a signed
Cf-Access-Jwt-Assertionheader that Cloudflare validates before forwarding to your origin. Nitro Repo now re-validates that signature against Cloudflare’s JWKS so the token cannot be spoofed.
Configure Nitro Repo to validate the Cloudflare Access token via JWKS:
[security]
allow_basic_without_tokens = false
[security.sso]
enabled = true
login_path = "/api/user/sso/login"
login_button_text = "Sign in with Cloudflare"
provider_login_url = "https://tv.sudoers.dev/cdn-cgi/access/login"
provider_redirect_param = "redirect_url"
auto_create_users = true
[[security.sso.providers]]
name = "cloudflare"
issuer = "https://<team>.cloudflareaccess.com"
audience = "<your-aud-tag>"
jwks_url = "https://<team>.cloudflareaccess.com/cdn-cgi/access/certs"
token_source = { kind = "header", name = "Cf-Access-Jwt-Assertion" }
email_claim = "email"
display_name_claim = "name"
role_claims = ["roles"]Cloudflare automatically blocks unauthenticated traffic, so Nitro Repo only receives requests that satisfy your Access policies. The JWKS-backed provider above re-validates the Cf-Access-Jwt-Assertion signature; no trust in plain headers is required.
Example: Google Workspace via OAuth2 Proxy
When fronting Nitro Repo with an OAuth2 reverse proxy (such as oauth2-proxy) that uses Google Workspace as the identity provider, configure the proxy to include headers containing the user’s email and name:
# oauth2-proxy excerpt
upstreams:
- https://nitro_repo:6742
providers:
- id: google-workspace
provider: google
clientID: ${GOOGLE_CLIENT_ID}
clientSecret: ${GOOGLE_CLIENT_SECRET}
scope: "openid email profile"
google:
adminEmail: admin@example.com
group: nitro-users@example.com
setAuthorizationHeader: true
passAccessToken: true
passAuthorization: true
passUserHeaders: true
setXAuthRequest: trueDocker Compose Example
Below is a minimal docker-compose setup that runs Nitro Repo behind oauth2-proxy and routes traffic through a single Nginx reverse proxy. OAuth2 proxy handles Google Workspace authentication and forwards identity headers to Nitro Repo.
services:
nitro_repo:
image: nitro_repo-nitro_repo:latest
env_file: .env
volumes:
- nitro_data:/data
networks: [nitro_net]
oauth2_proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
command:
- --http-address=0.0.0.0:4180
- --upstream=http://nitro_repo:6742
- --cookie-secret=${OAUTH2_PROXY_COOKIE_SECRET}
- --cookie-secure=true
- --cookie-domain=example.com
- --cookie-refresh=24h
- --cookie-expire=168h
- --reverse-proxy=true
- --pass-access-token=true
- --pass-authorization-header=true
- --set-authorization-header=true
- --set-xauthrequest=true
- --skip-provider-button=true
- --provider=google
- --client-id=${GOOGLE_CLIENT_ID}
- --client-secret=${GOOGLE_CLIENT_SECRET}
- --redirect-url=https://login.example.com/oauth2/callback
- --email-domain=example.com
- --scope=openid email profile
- --oidc-extra-audience=https://www.googleapis.com/auth/userinfo.email
environment:
OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
networks: [nitro_net]
nginx:
image: nginx:stable
depends_on:
- oauth2_proxy
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- 80:80
- 443:443
networks: [nitro_net]
networks:
nitro_net:
driver: bridge
volumes:
nitro_data:Example nginx.conf forwarding public traffic through oauth2-proxy:
server {
listen 80;
server_name login.example.com;
location / {
proxy_pass http://oauth2_proxy:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name tv.sudoers.dev;
location / {
proxy_pass http://oauth2_proxy:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}