A complete guide to implementing forward authentication with role-based access control for a Golang application using Zitadel (Identity Provider), Tinyauth (Forward Auth Middleware), and Traefik (Reverse Proxy).
This setup demonstrates how to secure a Golang web application using:
- Zitadel: Open-source identity and access management (IAM) platform that handles user authentication
- Tinyauth: Lightweight forward authentication middleware that integrates with reverse proxies
- Traefik: Modern reverse proxy that routes requests and enforces authentication
- Golang App: Simple application that displays authentication headers to verify the setup
Purpose: Local development and testing environment to showcase secure authentication patterns.
Note: This setup uses HTTP for local testing only. For production environments, HTTPS/TLS should be configured.
User Request → Traefik → Tinyauth (checks auth) → Zitadel (if not authenticated)
↓ ↓
Golang App ← Headers (user, email, groups) ← Access Token
- User accesses
APP_DOMAIN - Traefik intercepts the request and forwards it to Tinyauth
- Tinyauth checks if the user has a valid session
- If not authenticated, Tinyauth redirects to Zitadel login
- User logs in with Zitadel
- Zitadel returns OAuth tokens with user information and roles
- Tinyauth creates a session and forwards authentication headers to the app
- The Golang app receives headers:
Remote-User,Remote-Email,Remote-Groups
sequenceDiagram
participant U as User
participant T as Traefik
participant Ta as Tinyauth (Auth Proxy)
participant Z as Zitadel (Identity Provider)
participant A as Golang App
title User Authentication and Request Flow
U->>T: 1. Accesses APP_DOMAIN
T->>Ta: 2. Intercepts request and forwards (APP_DOMAIN)
Ta->>Ta: 3. Check for valid session/cookie
alt Not Authenticated
Ta->>U: 4. Redirect to Zitadel Login
U->>Z: 5. User logs in/authenticates
Z-->>U: 6. Returns OAuth Tokens (Code/Token)
U->>Ta: 7. Presents Tokens for session validation
Ta->>Z: 7. Validate token (UserInfo / Introspection)
Z-->>Ta: User Info (Email, Groups)
Ta->>Ta: 8. Create session/cookie
end
Ta->>A: 8. Forward request with Authentication Headers
Note right of Ta: Headers: Remote-User, Remote-Email, Remote-Groups
A-->>U: 9. Serve content (Authenticated)
Securing a new app involves the following 2 steps:
Protecting a new app involves the following steps:
- create a new docker compose file
- create an oauth application in Zitadel if required
- create a new tinyauth instance if required
Minimal setup:
services:
myapp:
build: .
restart: unless-stopped
container_name: myapp
expose:
- APP_PORT
labels:
traefik.enable: true
traefik.http.routers.myapp.rule: Host(`${APP_DOMAIN}`)
traefik.http.routers.myapp.entrypoints: websecure
traefik.http.routers.myapp.tls: true
traefik.http.routers.myapp.tls.certresolver: letsencrypt
traefik.http.routers.myapp.priority: 100
traefik.http.routers.myapp.middlewares: tinyauth-forward@docker
traefik.http.routers.myapp.service: myapp
traefik.http.services.myapp.loadbalancer.server.port: APP_PORT
networks:
- dokploy-network
networks:
dokploy-network:
external: true
This config uses the default tinyauth melkishengue-hq instance. This is fine if you don't need a oauth2 application specific for your app (you just need to authenticate users). If you need a separate app for the new service follow the next steps.
Now to go (Zitadel)[https://login-dailylesson.melkishengue.com/api/oauth/callback/zitadel] to create a new application.
Choose the organization and the project where the application should belong to and click on NEW.
Next:
- enter the name of the application. E.g. dailylesson
- select WEB and continue
- choose CODE to have auth code exchange flow and continue
- enter the following redirect url: https://login-dailylesson.melkishengue.com/api/oauth/callback/zitadel.
This url should match the
PROVIDERS_ZITADEL_REDIRECT_URLenv var configured in tinyauth docker compose configuration. Enter your apps url for the logout url, e.g. https://dailylesson.melkishengue.com. - next click create an copy the client id and client secret and add them to the dockerfile in the next step
Create a new tinyauth instance with the following minimal configuration:
services:
myapp:
build: .
restart: unless-stopped
container_name: myapp
expose:
- APP_PORT
environment:
# ... your envs
labels:
traefik.enable: true
traefik.http.routers.myapp.rule: Host(`${APP_DOMAIN}`)
traefik.http.routers.myapp.entrypoints: websecure
traefik.http.routers.myapp.tls: true
traefik.http.routers.myapp.tls.certresolver: letsencrypt
traefik.http.routers.myapp.priority: 100
traefik.http.routers.myapp.middlewares: myapp-tinyauth-forward@docker
traefik.http.routers.myapp.service: myapp
traefik.http.services.myapp.loadbalancer.server.port: APP_PORT
networks:
- dokploy-network
# oauth2 login screen
myapp-tinyauth:
image: ghcr.io/steveiliop56/tinyauth:latest
container_name: myapp-tinyauth
restart: unless-stopped
expose:
- 3000
environment:
SECRET: ${TINYAUTH_SECRET_KEY}
APP_URL: https://${TINYAUTH_DOMAIN}
PORT: 3000
LOG_LEVEL: trace
LOG_JSON: true
SECURE_COOKIE: true
OAUTH_AUTO_REDIRECT: zitadel
OAUTH_REDIRECT_URI: https://${APP_DOMAIN}
PROVIDERS_ZITADEL_CLIENT_ID: ${PROVIDERS_ZITADEL_CLIENT_ID}
PROVIDERS_ZITADEL_CLIENT_SECRET: ${PROVIDERS_ZITADEL_CLIENT_SECRET}
PROVIDERS_ZITADEL_AUTH_URL: https://${ZITADEL_DOMAIN}/oauth/v2/authorize
PROVIDERS_ZITADEL_TOKEN_URL: https://${ZITADEL_DOMAIN}/oauth/v2/token
PROVIDERS_ZITADEL_USER_INFO_URL: https://${ZITADEL_DOMAIN}/oidc/v1/userinfo
PROVIDERS_ZITADEL_REDIRECT_URL: https://${TINYAUTH_DOMAIN}/api/oauth/callback/zitadel
PROVIDERS_ZITADEL_SCOPES: openid profile email groups
PROVIDERS_ZITADEL_NAME: Zitadel
BACKGROUND_IMAGE: https://images.pexels.com/photos/3038813/pexels-photo-3038813.jpeg
APP_TITLE: myapp
USERS_ALLOW_ANY_OAUTH: true
labels:
traefik.enable: true
traefik.http.routers.myapp-tinyauth.rule: Host(`${TINYAUTH_DOMAIN}`)
traefik.http.routers.myapp-tinyauth.entrypoints: websecure
traefik.http.routers.myapp-tinyauth.tls: true
traefik.http.routers.myapp-tinyauth.tls.certresolver: letsencrypt
traefik.http.services.myapp-tinyauth.loadbalancer.server.port: 3000
traefik.http.middlewares.myapp-tinyauth-forward.forwardauth.address: http://myapp-tinyauth:3000/api/auth/traefik
traefik.http.middlewares.myapp-tinyauth-forward.forwardauth.trustForwardHeader: true
traefik.http.middlewares.myapp-tinyauth-forward.forwardauth.authResponseHeaders: remote-user,remote-email,remote-groups,remote-user-id,remote-name,Authorization
traefik.http.middlewares.myapp-tinyauth-forward.forwardauth.authRequestHeaders: Cookie,Authorization,Accept,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,X-Forwarded-Uri,X-Real-Ip
depends_on:
- myapp
networks:
- dokploy-network
networks:
dokploy-network:
external: trueReference values:
TINYAUTH_DOMAIN=login-dailylesson.melkishengue.com
APP_DOMAIN=dailylesson.melkishengue.com
ZITADEL_DOMAIN=auth.afribytes.com
PROVIDERS_ZITADEL_CLIENT_ID=
PROVIDERS_ZITADEL_CLIENT_SECRET=
TINYAUTH_SECRET_KEY=
Note: TINYAUTH_DOMAIN and APP_DOMAINneeds to have to have the same sub-domain otherwise the redirect to APP_DOMAIN will fail because of security reason on the frontend.