Files
ARUNAVO RAY 66e3284898 fix(sso): repair SSO login bounce + migrate to @better-auth/oauth-provider (#307)
Resolves #306. SSO sign-in via OIDC (Authentik / Keycloak / etc.) now links the
SSO identity to an existing email/password admin instead of bouncing to /login
with `?error=UNKNOWN`. Account-linking is gated on the operator-supplied
**Domain** field — cross-domain claims from a compromised IdP are refused.

Also bundles the deprecated `oidcProvider` → `@better-auth/oauth-provider`
migration. **Operators using the OAuth-provider feature must rotate registered
client secrets after upgrade** (legacy plaintext → hashed storage; see the
0012 migration notes).

Verified end-to-end on the pr-307 image against a real Authentik instance:
SSO login lands on the dashboard, `accounts` table gets both `credential` and
`authentik` rows for the same user. See PR description for full details.
2026-06-02 11:40:54 +05:30

7.4 KiB

Local SSO Testing Guide

This guide explains how to test SSO authentication locally with Gitea Mirror.

Setup Steps:

  1. Create a Google OAuth Application

    • Go to Google Cloud Console
    • Create a new project or select existing
    • Enable Google+ API
    • Go to "Credentials" → "Create Credentials" → "OAuth client ID"
    • Choose "Web application"
    • Add authorized redirect URIs:
      • http://localhost:3000/api/auth/sso/callback/google-sso
      • http://localhost:9876/api/auth/sso/callback/google-sso
  2. Configure in Gitea Mirror

    • Go to Configuration → Authentication tab
    • Click "Add Provider"
    • Select "OIDC / OAuth2"
    • Fill in:
      • Provider ID: google-sso
      • Email Domain: gmail.com (or your domain)
      • Issuer URL: https://accounts.google.com
      • Click "Discover" to auto-fill endpoints
      • Client ID: (from Google Console)
      • Client Secret: (from Google Console)
    • Save the provider

Option 2: Using Keycloak (Local Identity Provider)

Setup with Docker:

# Run Keycloak
docker run -d --name keycloak \
  -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:latest start-dev

# Access at http://localhost:8080
# Login with admin/admin

Configure Keycloak:

  1. Create a new realm (e.g., "gitea-mirror")
  2. Create a client:
    • Client ID: gitea-mirror
    • Client Protocol: openid-connect
    • Access Type: confidential
    • Valid Redirect URIs: http://localhost:*/api/auth/sso/callback/keycloak
  3. Get credentials from the "Credentials" tab
  4. Create test users in "Users" section

Configure in Gitea Mirror:

  • Provider ID: keycloak
  • Email Domain: example.com
  • Issuer URL: http://localhost:8080/realms/gitea-mirror
  • Client ID: gitea-mirror
  • Client Secret: (from Keycloak)
  • Click "Discover" to auto-fill endpoints

Option 3: Using Mock SSO Provider (Development)

For testing without external dependencies, you can use a mock OIDC provider.

Using oidc-provider-example:

# Clone and run mock provider
git clone https://github.com/panva/node-oidc-provider-example.git
cd node-oidc-provider-example
npm install
npm start

# Runs on http://localhost:3001

Configure in Gitea Mirror:

  • Provider ID: mock-provider
  • Email Domain: test.com
  • Issuer URL: http://localhost:3001
  • Client ID: foo
  • Client Secret: bar
  • Authorization Endpoint: http://localhost:3001/auth
  • Token Endpoint: http://localhost:3001/token

Testing the SSO Flow

  1. Logout from Gitea Mirror if logged in
  2. Go to /login
  3. Click on the SSO tab
  4. Either:
    • Click the provider button (e.g., "Sign in with gmail.com")
    • Or enter your email and click "Continue with SSO"
  5. You'll be redirected to the identity provider
  6. Complete authentication
  7. You'll be redirected back and logged in

Troubleshooting

Common Issues:

  1. "Invalid origin" error

    • Check that trustedOrigins in /src/lib/auth.ts includes your dev URL
    • Restart the dev server after changes
  2. Provider not showing in login

    • Check browser console for errors
    • Verify provider was saved successfully
    • Check /api/sso/providers returns your providers
  3. Redirect URI mismatch

    • Ensure the redirect URI in your OAuth app matches exactly: http://localhost:PORT/api/auth/sso/callback/PROVIDER_ID
  4. CORS errors

    • Add your identity provider domain to CORS allowed origins if needed

Debug Mode:

Note: Better Auth uses its own logger and does not read the DEBUG environment variable (it is not based on the debug npm package). An older version of this guide suggested DEBUG=better-auth:* — that has no effect.

Better Auth's logger defaults to the warn level, so SSO/OIDC sign-in and callback details are hidden. Set the log level to debug to surface the full trace:

# Local dev
BETTER_AUTH_LOG_LEVEL=debug bun run dev
# Docker Compose
services:
  gitea-mirror:
    environment:
      - BETTER_AUTH_LOG_LEVEL=debug

Then watch the server logs (e.g. docker compose logs -f gitea-mirror) while you attempt an SSO login. Lines are prefixed with [Better Auth]:. Accepted values are debug, info, warn, and error.

Debugging a login that bounces back to /login

If clicking the SSO button sends you to the provider and then straight back to the login screen, the OAuth flow itself usually succeeded but no session cookie was persisted. Work through these checks:

  1. Enable BETTER_AUTH_LOG_LEVEL=debug (above) and look for errors during the /api/auth/sso/callback/<provider-id> request.
  2. Check for ?error=UNKNOWN on the landing URL (or ?error=account%20not%20linked in dev). That's Better Auth's account-linking step refusing to attach the SSO identity to an existing email/password account. The debug log line to look for is User already exist but account isn't linked to <providerId>. The fix is almost always to set the SSO provider's Domain field to the email domain your users actually have — auto-linking is gated on that domain match. See docs/SSO-OIDC-SETUP.md#account-linking.
  3. Check the redirect URI registered in your IdP exactly matches https://<your-domain>/api/auth/sso/callback/<provider-id> (scheme, host, and provider ID — no trailing slash).
  4. Confirm the session cookie is set. In the browser DevTools → Network, inspect the callback response for a Set-Cookie: better-auth-session=… header, and DevTools → Application → Cookies for the stored cookie. Behind a reverse proxy, ensure BETTER_AUTH_URL is your external HTTPS URL so the cookie is issued with the correct domain and Secure flag, and that the proxy forwards X-Forwarded-Proto: https and X-Forwarded-Host.
  5. A 401 on /api/sso/applications is unrelated to client login — that endpoint backs the OAuth provider (consent) management UI and requires an existing session. It is not part of the Authentik/OIDC sign-in flow.

Testing Different Scenarios

1. New User Registration

  • Use an email not in the system
  • SSO should create a new user automatically

2. Existing User Login

  • Create a user with email/password first
  • Login with SSO using same email
  • Should link to existing account

3. Domain-based Routing

  • Configure multiple providers with different domains
  • Test that entering email routes to correct provider

4. Organization Provisioning

  • Set organizationId on provider
  • Test that users are added to correct organization

Security Testing

  1. Token Expiration

    • Wait for session to expire
    • Test refresh flow
  2. Invalid State

    • Modify state parameter in callback
    • Should reject authentication
  3. PKCE Flow

    • Enable/disable PKCE
    • Verify code challenge works

Using with Better Auth CLI

Better Auth provides CLI tools for testing:

# List registered providers
bun run auth:providers list

# Test provider configuration
bun run auth:providers test google-sso

Environment Variables

For production-like testing:

BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-key

Next Steps

After successful SSO setup:

  1. Test user attribute mapping
  2. Configure role-based access
  3. Set up SAML if needed
  4. Test with your organization's actual IdP