Skip to content

feat(auth): first-class generic OAuth2/OIDC provider (Microsoft Entra ID / Azure AD support) #487

Description

@jjmllr

Problem

Studio ships auth slots for GitHub, GitLab, Google OAuth, and a custom SSO server, but there is no provider for generic OIDC identity providers such as Microsoft Entra ID (formerly Azure AD), Okta, Auth0, Keycloak, etc. For organizations that authenticate via Entra, there is currently no documented, supported way to log into Studio.

The built-in sso provider can't fill the gap: it's hardwired to fixed /oauth/authorize, /oauth/token, /oauth/userinfo paths (sso.get.ts) and expects the bespoke nuxt-studio-sso response shape (including github_token), so it is incompatible with Entra's endpoints.

Current workaround

The google handler is actually a generic OAuth2/OIDC client whose endpoints are all overridable (google.get.ts). By pointing the Google slot at Entra endpoints, auth works today:

studio: {
  auth: {
    google: {
      clientId: process.env.STUDIO_GOOGLE_CLIENT_ID,
      clientSecret: process.env.STUDIO_GOOGLE_CLIENT_SECRET,
      authorizationURL: `https://login.microsoftonline.com/${process.env.ENTRA_TENANT_ID}/oauth2/v2.0/authorize`,
      tokenURL: `https://login.microsoftonline.com/${process.env.ENTRA_TENANT_ID}/oauth2/v2.0/token`,
      userURL: 'https://graph.microsoft.com/oidc/userinfo',
      scope: ['openid', 'email', 'profile'], // `openid` is mandatory or Entra returns no OIDC userinfo
    },
  },
}

This works because Entra's /oidc/userinfo returns standard OIDC claims (sub, name, email, picture) that match the GoogleUser interface. But it's a hack:

  • Untyped / undocumented — the public auth.google type only declares clientId and clientSecret. The URL overrides work purely by runtime accident; TS users get red squiggles and no autocomplete.
  • Mislabeled everywhere — the login button reads "Continue with Google" (admin.ts), the authorization gate uses STUDIO_GOOGLE_MODERATORS (google.get.ts), and the session is tagged provider: 'google' (google.get.ts).
  • Mutually exclusive with real Google OAuth — you can't run both, since there's only one slot.

Proposed solution

Add a first-class, provider-agnostic auth.oidc slot — essentially the existing google handler generalized and properly typed — and document Entra as the primary example. Google can remain as-is, or later become a thin preset over the generic client.

Config shape (module.ts)

auth?: {
  // ...existing github / gitlab / google / sso
  /**
   * Generic OAuth2 / OIDC provider (Microsoft Entra ID, Okta, Auth0, Keycloak, …).
   * Requires STUDIO_OIDC_MODERATORS plus a Git PAT (STUDIO_GITHUB_TOKEN / STUDIO_GITLAB_TOKEN)
   * to push changes, same model as Google OAuth.
   */
  oidc?: {
    /** @default process.env.STUDIO_OIDC_CLIENT_ID */
    clientId?: string
    /** @default process.env.STUDIO_OIDC_CLIENT_SECRET */
    clientSecret?: string
    /** Authorization endpoint. @default process.env.STUDIO_OIDC_AUTHORIZATION_URL */
    authorizationURL?: string
    /** Token endpoint. @default process.env.STUDIO_OIDC_TOKEN_URL */
    tokenURL?: string
    /** OIDC userinfo endpoint. @default process.env.STUDIO_OIDC_USER_URL */
    userURL?: string
    /** @default ['openid', 'email', 'profile'] */
    scope?: string[]
    /** Extra params appended to the authorization URL (e.g. { prompt: 'consent' }). */
    authorizationParams?: Record<string, string>
    /** Label shown on the login button. @default 'SSO' */
    label?: string
    /** Override redirect URL. @default `${hostname}/__nuxt_studio/auth/oidc` */
    redirectURL?: string
  }
}

Implementation checklist

  1. New route server/routes/auth/oidc.get.ts — generalize google.get.ts. Differences: env-var prefix STUDIO_OIDC_*, moderators from STUDIO_OIDC_MODERATORS, default scope of ['openid', 'email', 'profile'], and store provider: 'oidc' (or a configurable tag) in the session. Reuse requestAccessToken / generateOAuthState / validateOAuthState unchanged. Optionally add PKCE (mirroring sso.get.ts) since most enterprise IdPs prefer/require it.
  2. Register handler in module.ts/__nuxt_studio/auth/oidc.
  3. RuntimeConfig — add an oidc defaults block, include it in the config hash and the passthrough.
  4. Login UI — add a detection + button in admin.ts, using the configurable label.
  5. Validation — add an oidc branch to validateAuthConfig (auth.ts): require STUDIO_OIDC_MODERATORS + a Git PAT, identical to the Google branch.
  6. Docs — add a section to docs/content/4.auth-providers.md with the Entra example above (incl. the openid scope note and the redirect_uri the user must register in the Entra app).

Quick win regardless of scope

Even if the full generic provider is deferred, the URL-override fields (authorizationURL, tokenURL, userURL, scope, authorizationParams, redirectURL) should be added to the public auth.google type and documented — they already work at runtime, they're just invisible to TS users.

Alternatives considered

  • Dedicated auth.entra preset — lower friction for Entra specifically, but adds another near-duplicate handler and doesn't help Okta/Auth0/Keycloak users. A generic oidc slot subsumes it and a preset can be layered on later.
  • Extend sso — rejected; its fixed paths and nuxt-studio-sso-specific response shape make it the wrong abstraction for arbitrary IdPs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions