You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
First of all, thank you @sevensolutions for this amazing plugin!
I am working on a frontend application and an API, first time working with OIDC. Ideally, I want both the frontend app and the backend API (built with Huma) to not care about authN or authZ. What I have in mind is using Traefik + traefik-oidc-auth-app as reverse proxy for both, Zitadel for authN and Cerbos for authZ.
I made it work locally using the following configuration and using the 127-0-0-1.sslip.io domain to route to services:
What I do not like about this is the fact that the API does not have its own Zitadel application to auth requests and the fact that they are all under the same domain, which can make authZ a bit tricky. Ideally, anyone can send requests to the backend API, via Traefik, and Traefik's OIDC middleware must validate the token using Zitadel's introspection endpoint and the API's own clientID and clientSecret.
From what I understood, if I need different services to use different Zitadel applications, I need to create separate implementations of the traefik-oidc-auth middleware and configure them appropriately. I assume that I need to also have a different endpoint for each instance of the middleware. The one for frontend is going to listen to /oidc/callback, via Traefik PathPrefix, and the one for backend API listens to /oidc/api-callback (overriding the default value for the CallbackUri attribute of the plugin configuration).
That means that the whole workflow is going to look like this:
sequenceDiagram
participant U as 👤 User
participant B as 🌐 Browser
participant T as 🚪 Traefik Gateway
participant P as 🛡️ traefik-oidc-auth-app
participant PA as 🛡️ traefik-oidc-auth-api
participant Z as 🔐 ZITADEL
participant C as 🛡️ Cerbos
participant FE as 🖥️ Frontend App
participant BE as 🔧 Backend API
Note over U,BE: 🎯 Phase 1: User Authentication (OIDC Flow)
U->>B: 1. Navigates to example.com/app
B->>T: 2. GET example.com/app
T->>P: 3. Check authentication status
P->>P: 4. No valid session found
P->>B: 5. Redirect to ZITADEL for login
Note right of P: Redirect to:<br/>example.com/auth/oauth/v2/authorize<br/>?redirect_uri=example.com/oidc/callback
B->>T: 6. GET example.com/auth/oauth/v2/authorize
T->>Z: 7. Traefik routes to ZITADEL
Z->>B: 8. Returns ZITADEL login page
U->>B: 9. Enters username/password
B->>T: 10. POST credentials to ZITADEL (via Traefik)
T->>Z: 11. Routes to ZITADEL
Z->>Z: 12. Validates credentials
Z->>T: 13. Redirect with auth code
Note right of Z: Location: example.com/oidc/callback<br/>?code=abc123&state=xyz
T->>P: 14. Routes to traefik-oidc-auth-app callback
P->>Z: 15. Exchange code for tokens
Note right of P: POST zitadel:8080/oauth/v2/token<br/>grant_type=authorization_code<br/>code=abc123
Z->>P: 16. Returns access_token + id_token
P->>P: 17. Creates secure session + stores tokens
Note right of P: Sets httpOnly cookies<br/>Stores access_token in session
P->>B: 18. Redirect to original destination
Note right of P: Location: example.com/app<br/>Set-Cookie: session=secure_token
B->>T: 19. GET example.com/app (with session cookie)
T->>P: 20. Check authentication status
P->>P: 21. Valid session found ✅
P->>T: 22. Add user context headers
Note right of P: X-User-ID: user123<br/>X-User-Email: john@example.com<br/>X-User-Roles: admin,user
T->>FE: 23. Forward to frontend app
FE->>T: 24. Return frontend app
T->>B: 25. Serve frontend app
Note over U,BE: 🚀 Phase 2: API Calls (Token Introspection)
U->>B: 26. User action in frontend
B->>T: 27. AJAX call to API
Note right of B: GET example.com/api/user/profile<br/>Cookie: session=secure_token
T->>PA: 28. Check authentication for API route
PA->>PA: 29. Extract access_token from session
PA->>Z: 30. Introspect token with ZITADEL
Note right of PA: POST zitadel:8080/oauth/v2/introspect<br/>API's clientID/secret<br/>token=access_token_from_session
Z->>Z: 31. Validates token & checks permissions
Z->>PA: 32. Returns token validation result
Note right of Z: {"active": true, "sub": "user123",<br/>"scope": "read:profile", "exp": 1234567890}
alt Token Valid ✅
PA->>T: 33a. ✅ Token valid + user context
Note right of PA: X-User-ID: user123<br/>X-User-Roles: admin,user<br/>X-User-Email: john@example.com<br/>X-User-Permissions: read:profile
T->>C: 34a. Authorization check
Note right of T: POST cerbos:3593/api/check<br/>{"principal": {"id": "user123", "roles": ["admin","user"]},<br/>"resource": {"kind": "user", "id": "profile"},<br/>"actions": ["view"]}
C->>C: 35a. Evaluate authorization policy
Note right of C: Check if user:admin can view user:profile<br/>Based on CEL policy rules
C->>T: 36a. Authorization decision
Note right of C: {"requestId": "req123",<br/>"results": [{"resource": {...}, "actions": {"view": "EFFECT_ALLOW"}}]}
alt Authorization ALLOW ✅
T->>BE: 37a. Forward authorized request to backend
Note right of T: GET /user/profile<br/>X-User-ID: user123<br/>X-User-Roles: admin,user<br/>X-Authorized-Actions: view<br/>(Clean request!)
BE->>BE: 38a. Pure business logic
Note right of BE: user_id = headers['X-User-ID']<br/>actions = headers['X-Authorized-Actions']<br/>No auth/authz code needed!
BE->>T: 39a. Return API response
T->>B: 40a. Forward response to browser
B->>U: 41a. Update UI with data
else Authorization DENY ❌
T->>B: 37b. 403 Forbidden
Note right of T: User authenticated but not authorized<br/>for this specific action/resource
B->>U: 38b. Show "Access denied"
end
else Token Invalid/Expired ❌
PA->>B: 33b. 401 Unauthorized or redirect to login
Note right of PA: Token expired, revoked, or insufficient scope
B->>U: 34b. Show "Please login again"
end
Note over U,BE: 🔄 Every API call: AuthN (ZITADEL) → AuthZ (Cerbos) → Backend
Loading
Do you agree with the above architecture? Do you think I can leverage traefik-oidc-auth plugin for my use case? 😃
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
First of all, thank you @sevensolutions for this amazing plugin!
I am working on a frontend application and an API, first time working with OIDC. Ideally, I want both the frontend app and the backend API (built with Huma) to not care about authN or authZ. What I have in mind is using Traefik + traefik-oidc-auth-app as reverse proxy for both, Zitadel for authN and Cerbos for authZ.
I made it work locally using the following configuration and using the
127-0-0-1.sslip.io
domain to route to services:Docker compose + traefik configuration
traefik/dynamic.yml:
docker-compose.yml:
What I do not like about this is the fact that the API does not have its own Zitadel application to auth requests and the fact that they are all under the same domain, which can make authZ a bit tricky. Ideally, anyone can send requests to the backend API, via Traefik, and Traefik's OIDC middleware must validate the token using Zitadel's introspection endpoint and the API's own clientID and clientSecret.
From what I understood, if I need different services to use different Zitadel applications, I need to create separate implementations of the
traefik-oidc-auth
middleware and configure them appropriately. I assume that I need to also have a different endpoint for each instance of the middleware. The one for frontend is going to listen to/oidc/callback
, via Traefik PathPrefix, and the one for backend API listens to/oidc/api-callback
(overriding the default value for theCallbackUri
attribute of the plugin configuration).That means that the whole workflow is going to look like this:
Do you agree with the above architecture? Do you think I can leverage
traefik-oidc-auth
plugin for my use case? 😃Beta Was this translation helpful? Give feedback.
All reactions