Authenticate the Public API and webhooks with OAuth2 bearer tokens from the configured SSO IdP (client-credentials / M2M)

The idea is:

Allow authenticating the n8n Public REST API (/api/v1/...) and Webhooknode endpoints with Authorization: Bearer <access_token>, where thetoken is a standard OAuth2 / OIDC access token issued by the sameIdentity Provider that is already configured underSettings → SSO → OIDC (e.g. Microsoft Entra ID, Okta, Keycloak,Auth0, Google Workspace OIDC, Ping).

Scope:

  1. Public API (/api/v1/...) — primary ask.
  2. Production webhook endpoints — a new “IdP OAuth2” authentication
    option on the Webhook node, alongside the existing Header / Basic /
    JWT options.
  3. (Nice-to-have) The internal REST API, so scripts can reuse the
    editor API with a bearer token.

Validation rules (reusing the IdP n8n already trusts for login):

  • Fetch + cache JWKS from the IdP’s discovery document.
  • Verify signature, iss, aud, exp, nbf with small clock-skew
    tolerance.
  • Enforce an admin-configured expected audience (e.g.
    api://n8n-api) — required, not optional, to prevent confused-deputy
    where a token minted for a different app authenticates against n8n.
  • For client-credentials tokens: map azp / appid / oid to a
    dedicated service-principal identity in n8n with admin-configured
    instance/project scopes (or derived from roles / scope claims).
  • For user tokens (follow-up): resolve via the existing
    AuthIdentity (providerType: 'oidc') mapping used by the login
    flow, and reuse ProvisioningService.applySsoProvisioning so role
    claims behave consistently.
  • Existing X-N8N-API-KEY continues to work unchanged — this is
    additive, not a replacement.

Admin configuration (additive to the existing OIDC settings page):

  • Accept API calls with IdP bearer tokens — toggle.
  • Expected audience(s) — array.
  • Allowed client app IDs — array.
  • Default instance/project role for service principals.
  • Allow IdP OAuth2 auth on Webhook nodes — toggle.

My use case:

We run a self-hosted Enterprise n8n with OIDC SSO configured against
Microsoft Entra ID. The rest of our internal platform — APIs,
queues, databases, data-platform tooling — all accepts short-lived
Authorization: Bearer access tokens minted by Entra, with aud,
roles and conditional-access enforcement. n8n is the only
integration point that refuses those tokens and forces us to mint and
manage a parallel static secret.

A concrete example of what we want to do today and cannot:

  1. In Entra, register an “n8n API” application exposing
    api://n8n-api with app roles such as N8N.Workflow.Trigger.

  2. Register a daemon (“MyDaemon”) and grant it N8N.Workflow.Trigger.

  3. MyDaemon performs a client-credentials grant:

    POST https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
    grant_type=client_credentials
    client_id=<MyDaemon client id>
    client_secret=<secret>
    scope=api://n8n-api/.default
    
  4. MyDaemon calls n8n with the resulting token:

    POST /api/v1/workflows/<id>/run
    Authorization: Bearer eyJ...
    
  5. n8n returns 401, because the Public API only reads
    X-N8N-API-KEY.

Same problem on webhooks: we would like a Webhook node that says
“authenticate callers via our Entra-minted bearer tokens”, but the
Webhook node only offers Header / Basic / JWT and none of them
validate against the configured SSO IdP.

Result: every service that needs to talk to n8n has to carry an n8n
API key provisioned out-of-band, and we cannot use the Entra
application lifecycle (disable app → access revoked) to control
access to n8n. We have to either mint static keys and accept the
compliance findings, or front n8n with an API gateway that validates
Entra tokens and substitutes an API key — losing per-caller identity
inside n8n and adding infra every operator has to reinvent.

With this feature:

  1. Same Entra setup, no new app registrations.
  2. n8n admin enables the new toggle, sets expected audience
    api://n8n-api, allow-lists the MyDaemon client id.
  3. The exact same curl call succeeds. Identity is traceable
    (appid), scope is enforced (roles), and when the Entra admin
    disables the MyDaemon app registration, access stops within the
    token’s remaining lifetime — no manual key deletion in n8n.

I think it would be beneficial to add this because:

  1. Security — short-lived vs. long-lived credentials. n8n API keys
    are effectively forever. A leaked key (git commit, log line,
    compromised CI runner, misrouted webhook echo) grants durable
    access until a human notices and rotates it. IdP access tokens are
    short-lived (typically 5–60 min); a leaked token is a bounded
    window, not a permanent backdoor.

  2. Security — automatic, centralised revocation. Today, disabling
    a service account in Entra does NOT revoke its n8n API key.
    Offboarding a person does NOT invalidate their API keys. With
    IdP-validated tokens, disabling the app registration / user in the
    IdP stops the next token issuance and existing tokens expire on
    their own within minutes. Revocation becomes a property of the
    IdP, which is where it belongs.

  3. Security — inherited policy stack. Static API keys are a
    single unauthenticated factor. IdP-minted tokens inherit everything
    the IdP enforces on the caller: MFA, device compliance, conditional
    access, location / geo / risk signals (Entra Identity Protection),
    step-up auth, time-of-day policies.

  4. Auditability and forensics. API keys show up in audit logs as
    an opaque label on a shared service account. IdP tokens carry a
    real sub / appid / oid, so n8n audit logs can be correlated
    one-to-one with IdP sign-in logs (Entra Sign-in logs, Okta System
    Log) and the calling workload’s own logs. Much better for SOC /
    incident response / SOC 2 / ISO 27001 evidence.

  5. Secret sprawl. API keys have to be stored by every caller
    (Vault, Kubernetes Secrets, env vars, CI secrets, workstations).
    Every storage location is a potential breach point and needs its
    own rotation plumbing. Client-credentials and workload-identity
    tokens can be acquired on-the-fly via platform identity (Entra
    Managed Identity, AWS IRSA, GCP Workload Identity, SPIFFE / SPIRE)
    — often with no long-term secret on the caller at all.

  6. Least privilege. n8n API keys are effectively “god mode for
    that user” with no per-call scoping. IdP tokens carry
    scope / roles claims that can be narrowed per caller and per
    use case (e.g. a daemon only gets N8N.Workflow.Trigger, not
    N8N.Admin). n8n can enforce those claims at the middleware
    layer.

  7. Compliance. SOC 2, ISO 27001, PCI-DSS, HIPAA, FedRAMP all
    expect centralised identity, short-lived credentials, and
    auditable access. Long-lived per-app bearer tokens are a common
    audit finding. Many regulated environments explicitly ban
    long-lived bearer tokens for internal services; today those
    enterprises can’t use n8n at all without putting a gateway in
    front and losing per-caller identity inside n8n.

  8. Rotation in practice. Rotating an n8n API key is a manual,
    coordinated event (rotate in n8n → update every consumer →
    redeploy). Most orgs simply do not. IdP access tokens rotate
    transparently every few minutes; the underlying client secret can
    be rotated at the IdP with zero n8n-side changes; certificate-based
    client auth removes even that secret.

  9. Zero-trust alignment. n8n is currently an outlier in any
    zero-trust architecture: every other internal API accepts
    short-lived IdP tokens with aud validation and least-privilege
    scopes. Adding Bearer support on /api/v1 and webhooks brings n8n
    in line with standard enterprise practice.

  10. Low implementation cost. Most of the OAuth2 machinery is
    already in the codebase (see Resources below). The delta is a
    new middleware + a second security scheme in the OpenAPI spec +
    a Webhook node auth option — not a new dependency or a redesign.


Any resources to support this?

Existing n8n code that already does 90% of what’s needed — this
feature is essentially “wire up what’s already there”:

  • packages/cli/src/modules/sso-oidc/oidc.service.ee.ts — already
    performs OIDC discovery, caches the Configuration, handles
    HTTP(S) proxies, and uses the openid-client library, which
    exposes the JWKS / JWT verification primitives that this feature
    would reuse. No new dependency required.
  • packages/cli/src/modules/provisioning.ee/...
    ProvisioningService.applySsoProvisioning already maps claims →
    instance role → project roles. Can be reused directly.
  • packages/cli/src/public-api/index.ts and
    packages/cli/src/public-api/v1/openapi.yml — already use
    express-openapi-validator, which supports multiple security
    schemes. A new BearerAuth scheme can sit next to ApiKeyAuth
    without breaking any existing API-key client.
  • packages/cli/src/modules/mcp/... — already implements the
    “Bearer token via Express middleware” pattern; today it’s scoped
    to n8n-issued tokens (aud=mcp-server-api), but it’s the exact
    shape a sibling middleware for IdP-issued tokens would take.

Standards and references:

Community context:

Caveats to be explicit about up front:

  • Audience validation is mandatory (confused-deputy protection).
  • Short JWKS cache TTL (few minutes) to follow IdP key rotation.
  • Small clock-skew tolerance on exp / nbf (~30 s).
  • No raw JWTs in logs — log iss, sub, appid, jti only.
  • Not a substitute for public/anonymous webhook authentication;
    signed-secret HMAC patterns remain correct for those.
  • Short-lived ≠ un-stealable within the token lifetime over TLS;
    optional future enhancement is sender-constrained tokens via DPoP
    (RFC 9449) or mTLS-bound tokens (RFC 8705).

Are you willing to work on this?

Yes. Happy to contribute a PR against packages/cli/src/public-api,
packages/cli/src/modules/sso-oidc, and the Webhook node:

  1. New IdpBearerAuthService that wraps OidcService and performs
    JWT verification + claim mapping (signature, iss, aud, exp,
    nbf, audience allow-list, client-ID allow-list).
  2. New BearerAuth security scheme in
    packages/cli/src/public-api/v1/openapi.yml, and registration of
    the middleware in packages/cli/src/public-api/index.ts alongside
    the existing ApiKeyAuth handler.
  3. New “IdP OAuth2” option on the Webhook node authentication
    dropdown, wired to the same service.
  4. Extension of the existing OIDC SSO settings page with the new
    admin toggles and lists.
  5. Unit tests for the middleware (token validation, audience /
    app-ID allow-listing, claim mapping) and integration tests
    against a mock IdP.

Would appreciate early direction from the n8n Identity team on:

  • Preferred configuration model — per-app allow-list vs. pure
    audience check vs. both (current proposal: both, with audience
    required and app-ID allow-list optional for per-caller scoping).
  • Preferred identity representation for service principals inside
    n8n — a dedicated “service account” user entity, or a project-scoped
    principal with no User row.
  • Whether the admin UI should live inside the existing OIDC settings
    page or as a separate “API Authentication” tab.

Thanks for reading!

This is a well-thought-out proposal. A couple of things from someone who’s dealt with the same M2M auth gap in self-hosted n8n:

Your validation rules are solid. One addition worth considering: token introspection as a fallback for opaque tokens. Some enterprise IdPs (especially older ADFS setups) still issue opaque access tokens for client-credentials grants instead of JWTs. Supporting RFC 7662 introspection alongside local JWT validation covers that edge case without much added complexity.

On the service-principal mapping: the azp/appid → n8n identity mapping is the right call. One pattern that works well in practice is a lightweight “service account” entity in n8n that maps 1:1 to an IdP app registration. Each service account gets its own project/instance scope. This avoids the anti-pattern of mapping multiple M2M clients to a single “service” user with god-mode permissions.

The confused-deputy prevention via required aud is critical and I’m glad you called it out explicitly. Too many internal platforms skip this and end up with tokens minted for Service A being replayed against Service B.

One practical concern with the Webhook node auth option: you’ll want to think about token caching carefully. If every incoming webhook triggers a JWKS fetch + full JWT validation, you’ll add 50-200ms latency per request. Caching the JWKS (which you mentioned) plus caching validated token claims for the remaining TTL would keep it fast.

Would definitely use this if it lands. The current API key situation is the weakest link in an otherwise solid self-hosted security posture.