Zitadel
Sign in with Zitadel (OIDC)
medusa-auth-zitadel
Zitadel authentication provider for Medusa v2.
A standalone Auth Module Provider that lets customers, admin users, or custom actor types log in with Zitadel using the OpenID Connect Authorization Code flow.
Zitadel endpoints are resolved through OIDC Discovery (Copy to clipboard{issuer}/.well-known/openid-configuration), so the provider works with Zitadel Cloud and self-hosted instances behind a custom domain. The returned Copy to clipboardid_token is verified cryptographically against Zitadel's JWKS.
Requirements
- Medusa v2 (Copy to clipboard
@medusajs/framework^2.x) - Node.js 20+
- A running Zitadel instance (Cloud or self-hosted)
Installation
1npm install medusa-auth-zitadel
Zitadel setup
- In your Zitadel instance, create (or open) a Project.
- Add a new application:
- Type: Web
- Authentication method: Code — this uses Copy to clipboard
client_secret_basic, matching the provider's default.
- Set the Redirect URI to exactly the Copy to clipboard
callbackUrlyou will configure below (e.g. Copy to clipboardhttps://your-storefront.com/auth/zitadel/callback). It must match character for character — Zitadel rejects any mismatch during the token exchange. - Copy the generated Client ID and Client Secret.
Configuration
Register the provider in the Auth Module's Copy to clipboardproviders array in Copy to clipboardmedusa-config.ts:
1234567891011121314151617181920import { defineConfig, Modules } from "@medusajs/framework/utils"module.exports = defineConfig({// ...modules: [{resolve: "@medusajs/medusa/auth",options: {providers: [// Keep emailpass if you still need email/password login:// passing `providers` replaces the default list.{ resolve: "@medusajs/medusa/auth-emailpass", id: "emailpass" },{resolve: "medusa-auth-zitadel",id: "zitadel",options: {issuer: process.env.ZITADEL_ISSUER,clientId: process.env.ZITADEL_CLIENT_ID,clientSecret: process.env.ZITADEL_CLIENT_SECRET,callbackUrl: process.env.ZITADEL_CALLBACK_URL,
The Copy to clipboardid (Copy to clipboardzitadel) is the value used in authentication route paths.
Options
Option Type Required Default Description Copy to clipboardissuer Copy to clipboardstring Yes — Zitadel issuer URL, e.g. Copy to clipboardhttps://myinstance.zitadel.cloud. Trailing slashes are normalised. Copy to clipboardclientId Copy to clipboardstring Yes — OIDC client ID of the Zitadel application. Copy to clipboardclientSecret Copy to clipboardstring Yes — OIDC client secret of the Zitadel application. Copy to clipboardcallbackUrl Copy to clipboardstring Yes — URL Zitadel redirects to after authentication. Can be overridden per request with a Copy to clipboardcallback_url body parameter. Copy to clipboardscope Copy to clipboardstring No Copy to clipboardopenid profile email Scopes requested from Zitadel. Copy to clipboardclientAuthMethod Copy to clipboard"client_secret_basic" | "client_secret_post" No Copy to clipboardclient_secret_basic How the client authenticates at the token endpoint. Must match the Zitadel application's authentication method.
Environment variables
Variable Maps to Copy to clipboardZITADEL_ISSUER Copy to clipboardissuer Copy to clipboardZITADEL_CLIENT_ID Copy to clipboardclientId Copy to clipboardZITADEL_CLIENT_SECRET Copy to clipboardclientSecret Copy to clipboardZITADEL_CALLBACK_URL Copy to clipboardcallbackUrl
Actor types
By default only the Copy to clipboarduser actor type is restricted to Copy to clipboardemailpass; other actor types can authenticate with any installed provider. To control which providers each actor type may use, set Copy to clipboardprojectConfig.http.authMethodsPerActor. Note that specifying this option overrides the default, so any actor type you omit can no longer authenticate with any provider:
12345678910module.exports = defineConfig({projectConfig: {http: {authMethodsPerActor: {user: ["emailpass", "zitadel"],customer: ["emailpass", "zitadel"],},},},})
Authentication flow
The provider implements the standard Medusa third-party authentication flow. Replace Copy to clipboardcustomer with your actor type (Copy to clipboarduser, Copy to clipboardcustomer, or a custom actor type).
1. Start the flow. The storefront asks Medusa for the Zitadel authorize URL:
123curl -X POST http://localhost:9000/auth/customer/zitadel \-H 'Content-Type: application/json' \--data-raw '{ "callback_url": "https://your-storefront.com/auth/zitadel/callback" }'
The response contains a Copy to clipboardlocation property — the Zitadel authorize URL. Redirect the browser to it. Omit Copy to clipboardcallback_url to use the configured Copy to clipboardcallbackUrl.
123{"location": "https://myinstance.zitadel.cloud/oauth/v2/authorize?client_id=...&redirect_uri=...&response_type=code&scope=openid+profile+email&state=..."}
2. Zitadel redirects back. After the user authenticates, Zitadel redirects the browser to your Copy to clipboardcallback_url with Copy to clipboard?code=...&state=.... The storefront forwards both to Medusa:
123curl -X POST http://localhost:9000/auth/customer/zitadel/callback \-H 'Content-Type: application/json' \--data-raw '{ "code": "<code>", "state": "<state>" }'
3. Receive the Medusa token. On success, Medusa returns a JWT and the auth identity:
12345678{"token": "<medusa-jwt>","authIdentity": {"id": "au_zitadel_...","provider_metadata": {},"user_metadata": { "email": "user@example.com", "name": "Jane Doe" }}}
Use Copy to clipboardtoken as the Copy to clipboardAuthorization: Bearer <token> header for authenticated store or admin requests. If the auth identity does not exist yet, the provider creates it on first successful login.
The provider's Copy to clipboardstate value (containing the callback URL used as Copy to clipboardredirect_uri) is stored server-side via the Auth Module and consumed on callback, which protects the flow against CSRF and session replay.
Claim mapping
Zitadel's claims are mapped into Medusa's auth identity as follows.
Zitadel claim Medusa field Copy to clipboardsub Copy to clipboardentity_id Copy to clipboardemail Copy to clipboarduser_metadata.email Copy to clipboardemail_verified Copy to clipboarduser_metadata.email_verified Copy to clipboardname Copy to clipboarduser_metadata.name Copy to clipboardgiven_name Copy to clipboarduser_metadata.given_name Copy to clipboardfamily_name Copy to clipboarduser_metadata.family_name Copy to clipboardpreferred_username Copy to clipboarduser_metadata.preferred_username Copy to clipboardpicture Copy to clipboarduser_metadata.picture Copy to clipboardlocale Copy to clipboarduser_metadata.locale
Only claims actually returned by Zitadel are stored.
Troubleshooting
Symptom Cause / fix Copy to clipboardCould not fetch Zitadel OIDC discovery document Copy to clipboardissuer is wrong or unreachable. It must be the Zitadel base domain, e.g. Copy to clipboardhttps://myinstance.zitadel.cloud — not the token or authorize endpoint. Copy to clipboard... discovery document issuer "..." does not match configured issuer "..." The Copy to clipboardissuer returned by discovery differs from your setting (Zitadel may add or omit a trailing slash, or you are using a different hostname than the one Zitadel self-reports). Use the issuer exactly as Zitadel reports it. Copy to clipboardCould not exchange token with Zitadel: 400 Usually a redirect-URI mismatch. The Copy to clipboardcallbackUrl must exactly match a Redirect URI registered on the Zitadel application, including scheme, host, and path. Copy to clipboardCould not exchange token with Zitadel: 401 Copy to clipboardclientAuthMethod does not match the Zitadel application's authentication method. Zitadel Web apps default to Copy to clipboardclient_secret_basic; if you configured Post, set Copy to clipboardclientAuthMethod: "client_secret_post". Copy to clipboardCould not verify ... id_token The Copy to clipboardid_token audience or issuer did not match. Confirm Copy to clipboardclientId belongs to the Zitadel application you are authenticating against. Copy to clipboardZitadel does not support registration Expected. Zitadel SSO requires users to be provisioned in Zitadel first; there is no registration route. User can authenticate in Zitadel but gets a Medusa 401 The actor type is not allowed to use Copy to clipboardzitadel. Check Copy to clipboardprojectConfig.http.authMethodsPerActor.
Publishing
For maintainers, from the repository root:
12npm loginnpm publish --access public
The Copy to clipboardprepublishOnly script runs the build automatically. After publishing, submit the package to the Medusa Integrations directory.
License
MIT
