feat: multi-provider OAuth support (Google, Microsoft, Auth0, Keycloak)#353
Closed
feat: multi-provider OAuth support (Google, Microsoft, Auth0, Keycloak)#353
Conversation
- Add OAuth handlers for Google, Microsoft, Auth0, and Keycloak - Add shared authorization utility with checkAuthorization() pure function and isUserAuthorized() Nuxt runtime config wrapper - Authorization is checked BEFORE setUserSession() — unauthorized users never receive a session cookie - Update GitHub OAuth handler with authorization check and login field - Add NUXT_PUBLIC_REQUIRE_AUTH and NUXT_PUBLIC_AUTH_PROVIDERS env vars - Add NUXT_AUTHORIZED_USERS and NUXT_AUTHORIZED_EMAIL_DOMAINS for optional application-level allowlisting (any provider) - Keep NUXT_PUBLIC_USING_GITHUB_AUTH for backwards compatibility - Add dynamic provider login buttons in MainComponent.vue - Add PAT-mode shield icon button that opens dialog listing OAuth options - Update nuxt.config.ts with runtimeConfig for all five providers - Add 18 unit tests for authorization logic - Update DEPLOYMENT.md: comprehensive Authentication section for all five providers (PAT, GitHub, Google, Microsoft, Auth0, Keycloak) with setup steps, env var reference table, and authorization docs - Update README.md: deprecate NUXT_PUBLIC_USING_GITHUB_AUTH, document all new auth env vars with provider table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add server/modules/github-app-auth.ts: signs RS256 JWTs with Node.js built-in Web Crypto (no external deps), exchanges for installation token, caches with 5-min refresh buffer before expiry - Update authentication.ts: GitHub App is now priority 2 in the credential chain (mock → App token → PAT → user OAuth session token) - Add githubAppId/githubAppPrivateKey/githubAppInstallationId to nuxt.config.ts runtimeConfig - Document new vars in .env, README.md, and DEPLOYMENT.md - DEPLOYMENT.md: new 'GitHub App Installation Token' section with step-by-step App creation, private key export, and env var setup This enables Google/Microsoft/Auth0/Keycloak users to access the dashboard without any user-owned PAT — the installation token is machine-issued and scoped to exactly the required org permissions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- github-app-auth.ts: replace single global cache with per-installation Map; add paginated listAppInstallations() with 5-min TTL + thundering-herd dedup; getGitHubAppToken() resolves org from githubOrg/githubEnt query param → config → single-install auto-select; no installation ID config needed - server/api/installations.get.ts: new auth-protected endpoint returning accessible orgs (Flow 2: App JWT; Flow 1: session.organizations) - server/routes/auth/github.get.ts: redirect to /select-org when multiple installations found (was always redirecting to first org) - app/pages/select-org.vue: blocking org picker page; single org auto- navigates, multiple shows Vuetify v-select + Continue button - app/router.options.ts: add /select-org route - nuxt.config.ts: remove githubAppInstallationId (no longer needed) - .env, README.md, DEPLOYMENT.md: update docs to reflect auto-discovery Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Auth When a GitHub OAuth user logs in, call GET /user/installations with their token so the org picker only shows orgs they have access to — not all 48 marketplace installations. Priority order in /api/installations: 1. GitHub OAuth session token → /user/installations (user-filtered) 2. session.organizations (pre-populated at GitHub login) 3. App JWT → list ALL (fallback for non-GitHub OAuth / unauthenticated) Also updated github.get.ts to always pre-populate session.organizations (removed isPublicApp guard) so the picker page doesn't need a second API call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…place apps For private/internal apps (NUXT_PUBLIC_IS_PUBLIC_APP not set), listing all installations via App JWT is correct — there are only a few known orgs. Only marketplace/public apps need user-filtered results, since the App JWT would otherwise return every org that ever installed the app from the marketplace. When NUXT_PUBLIC_IS_PUBLIC_APP=true: 1. GitHub OAuth token in session → /user/installations (live, user-scoped) 2. session.organizations (pre-populated at GitHub login, used as fallback) When NUXT_PUBLIC_IS_PUBLIC_APP=false (default): - App JWT lists all installations (private app, small known set) - Falls back to session.organizations for GitHub OAuth without App key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
|
||
| const key = createPrivateKey({ key: normalisePem(privateKeyPem), format: 'pem' }) | ||
| const sign = createSign('RSA-SHA256') | ||
| sign.update(signingInput) |
|
|
||
| // GitHub App installation token (preferred for decoupled auth — no PAT needed) | ||
| // Client ID can be used as the App ID per GitHub docs, so check either | ||
| const appId = config.githubAppId || config.oauth?.github?.clientId || '' |
…e apps Public/marketplace apps (NUXT_PUBLIC_IS_PUBLIC_APP=true): - App JWT lists ALL marketplace installs — useless for individual users - Exception: GitHub OAuth login → /user/installations returns user-filtered list - For all other login methods (Google, Microsoft, etc.): return empty list and show a manual text input so the user can type their org slug Private/internal apps (default): - App JWT lists a small, known set of installed orgs → show dropdown This inverts the previous logic: the dropdown is for private apps where the install list is meaningful; the text input is the fallback for public apps where listing all installs would be misleading. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Without this check, any authenticated user (including Google/Microsoft OAuth) could access Copilot metrics for ANY org that installed the marketplace app by simply passing ?githubOrg=target-org. Fix: when NUXT_PUBLIC_IS_PUBLIC_APP=true and no default org is configured, the middleware verifies that the requested org is present in session.organizations — a list populated at GitHub OAuth login via GET /user/installations, which GitHub scopes to the user's own orgs. Consequences: - GitHub OAuth users: can only access orgs from their /user/installations - Non-GitHub OAuth (Google, Microsoft, etc.): session.organizations is empty → 403 for all org-specific API requests. Users must sign in with GitHub to prove org membership, or an admin must set NUXT_PUBLIC_GITHUB_ORG to pin to a specific org. The check is a no-op when NUXT_PUBLIC_IS_PUBLIC_APP is not set (private internal apps) or when a default org is configured via env vars. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nstallations auth When NUXT_PUBLIC_IS_PUBLIC_APP=true and NUXT_GITHUB_APP_PRIVATE_KEY is set, throw a 500 error immediately. The two modes are fundamentally incompatible: a private key grants app-level access to ALL marketplace installations, while public apps should use the user's own GitHub OAuth token (naturally user-scoped). Changes: - installations.get.ts: throw 500 misconfiguration error instead of silently using the JWT path; remove try/catch that was swallowing errors - middleware/github.ts: remove session.organizations authorization check — no longer needed since the incompatible config combo is blocked at the source - github.get.ts: remove session.organizations storage — not needed for security, kept inline installations fetch only for redirect logic (single org → direct, multiple → /select-org) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When requireAuth/usingGithubAuth/isPublicApp is set and no session exists, index.vue was unconditionally redirecting to /select-org before the user logged in. /api/installations then returned 401. Fix: only redirect to /select-org when auth is not required OR the user is already logged in. When auth is required and there is no session, let MainComponent render — it shows the login overlay, and after OAuth the callback redirects to /select-org or directly to the org as before. Also add a safety net in select-org.vue: if a 401 is returned (e.g. session expired, direct URL navigation), redirect back to / so the login overlay is displayed rather than showing a raw error message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- installations.get.ts: catch JWT/network errors and return [] so the UI shows a manual text input instead of an error page - github-app-auth.ts: move installationsInflight = null into finally block so a failed promise is never cached (previously caused permanent failure until server restart) - github-app-auth.ts: add diagnostic log in buildAppJwt showing whether a numeric App ID or Client ID is used as JWT issuer, with a clearer error message when PEM signing fails Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
useRouter() is auto-imported by Nuxt but must be called to get the instance. Without this, all router.push/replace calls silently failed, making the Continue button appear to do nothing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Default GitHub OAuth scope to ['read:user'] so the authorize URL is never built with an empty scope= param (GitHub returns 404) - Remove /user/installations call from github.get.ts onSuccess handler; redirect all users to /select-org instead, which already uses the App JWT (private app) or user token (public app) correctly via /api/installations — fixes 404 for GitHub users not in the org - Use config.oauth?.github?.clientId as JWT issuer fallback so NUXT_GITHUB_APP_ID is not required when NUXT_OAUTH_GITHUB_CLIENT_ID is already set Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| /** Build a short-lived JWT for GitHub App API calls. */ | ||
| function buildAppJwt(appId: string, privateKey: string): string { | ||
| const now = Math.floor(Date.now() / 1000) | ||
| console.log(`[github-app-auth] Building JWT with App ID ${appId}`) |
NUXT_PUBLIC_IS_PUBLIC_APP=true now implies authentication is required, consistent with NUXT_PUBLIC_USING_GITHUB_AUTH. Previously the server middleware and MainComponent login overlay both missed isPublicApp in their requireAuth checks, causing unauthenticated requests to fall through and throw a generic 500 instead of redirecting to GitHub OAuth. Changes: - server/middleware/github.ts: add isPublicApp to requireAuth - app/components/MainComponent.vue: add isPublicApp to isAuthRequired and to the activeProviders fallback (defaults to GitHub provider) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, setting NUXT_PUBLIC_AUTH_PROVIDERS=github alone was not enough — you also needed NUXT_PUBLIC_REQUIRE_AUTH=true or the legacy NUXT_PUBLIC_USING_GITHUB_AUTH=true to enforce authentication. Now any non-empty AUTH_PROVIDERS value implies requireAuth, so the minimum OAuth setup is: NUXT_PUBLIC_AUTH_PROVIDERS=github NUXT_OAUTH_GITHUB_CLIENT_ID=... NUXT_OAUTH_GITHUB_CLIENT_SECRET=... NUXT_PUBLIC_USING_GITHUB_AUTH remains supported for backwards compat but is no longer needed for new deployments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- NUXT_PUBLIC_AUTH_PROVIDERS being set now implies authentication required; NUXT_PUBLIC_REQUIRE_AUTH is no longer needed and hidden from docs - NUXT_PUBLIC_USING_GITHUB_AUTH removed from docs (kept in code for compat) - NUXT_PUBLIC_IS_PUBLIC_APP: org picker always shows text input (no server- side /user/installations enumeration); user types org slug directly - .env.example: cleaned up auth section, one comment explains AUTH_PROVIDERS - README + DEPLOYMENT: removed REQUIRE_AUTH and USING_GITHUB_AUTH entries, stripped REQUIRE_AUTH=true from all provider config examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces PR #245 with a clean implementation on top of current
main(v3.5.0). Activates OAuth provider support innuxt-auth-utils— providers are enabled entirely by environment variables, no code changes required to switch.What's new
Auth providers
/auth/googlehandler/auth/microsofthandler; settingNUXT_OAUTH_MICROSOFT_TENANTrestricts to your org's AAD tenant automatically/auth/auth0handler; acts as identity aggregator (GitHub, Google, LDAP, SAML, etc.)/auth/keycloakhandler; self-hosted OIDC for air-gapped/regulated environmentsAuthorization utility
server/utils/authorization.ts— sharedcheckAuthorization()pure function +isUserAuthorized()Nuxt wrappersetUserSession()— unauthorized users never receive a session cookie (fixes ordering bug in original PR Support for Authentication Schemes - Decouple user auth from GitHub API credentials #245)NUXT_AUTHORIZED_USERS— comma-separated logins/emails allowlistNUXT_AUTHORIZED_EMAIL_DOMAINS— comma-separated domain allowlistNew env vars
NUXT_PUBLIC_REQUIRE_AUTHNUXT_PUBLIC_USING_GITHUB_AUTH(backwards compat kept)NUXT_PUBLIC_AUTH_PROVIDERSgithub,google,microsoft,auth0,keycloakNUXT_AUTHORIZED_USERSNUXT_AUTHORIZED_EMAIL_DOMAINSUI
Documentation
NUXT_PUBLIC_USING_GITHUB_AUTH, added provider variable reference tableTesting
checkAuthorization()— all passingNot included (deferred)
Closes #244
Supersedes #245