Skip to content

nihalgonsalves/oidc-cookie-forward-auth

OIDC Cookie Forward-Auth

This is a simple forward-auth middleware for Traefik that handles cookie-based auth on behalf of a user authenticated via OpenID Connect.

Apps such as tinyauth or oauth2-proxy can protect unauthenticated apps or apps that support auth via a plain header, but do not provide support for stateful authentication.

This forward-auth middleware keeps track of cookies as part of the forward-auth session, and provides them to the reverse-proxy forward-auth handler, to be used in the origin request.

The forward-auth endpoint / server is never exposed. Everything is handled via the forward-auth middleware.

Setup

Prerequisites

  • A reverse proxy with forward-auth support (only Traefik is currently supported and tested). See Limitations.
  • An OIDC provider, such as Pocket ID. Any provider conforming to the OIDC specification should work.

Configuration reference

Environment variables

Name Description
CLIENT_ID OAuth2 Client ID
CLIENT_SECRET OAuth2 Client Secret
OIDC_ISSUER_CONFIG_URL Fully qualified OIDC issuer URL, e.g. https://auth.example.com/
SQLITE_PATH Database file, e.g. /var/lib/oidc/db/sessions.db, defaults to in-memory if not set
DOMAIN_BASE Base domain, used to shorten the config filenames
UNSAFE_COOKIE_INSECURE Can be set to true or 1 to enable using insecure session cookies

Configuration files

You must create a new config file in /var/lib/oidc/config/ for each host you want to support.

For example, if you have https://whoami.example.com, set your DOMAIN_BASE to .example.com, and create a config file in at /var/lib/oidc/config/whoami.ts.

Step-by-step

[!TIP] There's a complete example in the ./docs/example/ directory

  1. Make sure you have Traefik set up with SSL correctly

  2. Create a new OIDC client in your provider. The redirect URL is https://whoami.example.com/oauth2/callback – the same host as your regular service. The forward-auth proxy itself is not exposed.

    If you need to protect multiple hosts, you can set a wildcard redirect URL, list all the hosts, or create a client per host (as well as a forward-auth container and middleware per host).

  3. Add the forward-auth container and the middleware to the service you want to protect:

    services:
      # ...
      oidc-cookie-forward-auth:
        image: ghcr.io/nihalgonsalves/oidc-cookie-forward-auth:latest
        # depends_on:
        #   pocket-id:
        #     condition: service_healthy
        restart: unless-stopped
        env_file:
          # this file should contain:
          # CLIENT_ID=...
          # CLIENT_SECRET=...
          - .env.oidc-cookie-forward-auth
        environment:
          # you can omit this and the DB volume if you'd like sessions not to
          # be persisted across container restarts
          SQLITE_DB: /var/lib/oidc/db/sessions.db
          OIDC_ISSUER_CONFIG_URL: "https://auth.example.com/.well-known/openid-configuration"
          DOMAIN_BASE: ".example.com"
        volumes:
          - oidc-db:/var/lib/oidc/db
          # or other local path to store the config
          - ./config/:/var/lib/oidc/config
        expose:
          - 3000
        labels:
          traefik.enable: true
          traefik.http.middlewares.oidc-cookie-forward-auth.forwardauth.address: http://oidc-cookie-forward-auth:3000/oauth2/traefik
          traefik.http.middlewares.oidc-cookie-forward-auth.forwardauth.authResponseHeaders: cookie
    
    # ...
    
    whoami:
      depends_on:
        oidc-cookie-forward-auth:
          condition: service_healthy
      image: traefik/whoami:latest
      restart: unless-stopped
      labels:
        traefik.enable: true
        traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
        traefik.http.routers.whoami.entrypoints: websecure
        traefik.http.routers.whoami.middlewares: oidc-cookie-forward-auth
    
    volumes:
      oidc-db:
  4. Create a config file:

    // ./config/whoami.ts
    export const config = {
    	// this is any function that returns a fetch Response containing
    	// the Set-Cookie headers
    	getUpstreamCookies: () =>
    		fetch(new URL("http://whoami:80/auth/signin"), {
    			method: "POST",
    			body: new URLSearchParams({
    				// you can also add env variables to the cookie auth file
    				// and reference them using process.env.NAME
    				username: "admin",
    				password: "password",
    			}),
    		}),
    	// this is a validation request to make sure the session is still valid.
    	// use any URL that is only accessible when logged in.
    	// this enables the user to use logout functionality inside the origin
    	// app and seamlessly invalidate the OIDC session too.
    	validateUpstreamSession: async (headers: Bun.HeadersInit) => {
    		try {
    			const response = await fetch(new URL("http://whoami:80/me"), {
    				headers,
    				redirect: "manual",
    			});
    
    			return response.ok;
    		} catch {
    			return false;
    		}
    	},
    };
  5. That's it. Start the container, and visit your service, for example https://whoami.example.com/. You should be redirected to the OIDC provider.

    Once logged in there, getUpstreamCookies will be called to log in to the origin service, creating a session in the forward-auth service.

    Every subsequent request will be re-validated using validateUpstreamSession, and then the valid cookies will be sent to the reverse proxy to be provided to the origin request.

Limitations

  • This has only been tested with Traefik, but Caddy and nginx also support forward-auth and should work.
  • While an upstream session is unique to a browser session, this doesn't support any sub-configuration based on the user (or for e.g. groups). Access is allowed as long as the OIDC provider authorises the user to access this client.

About

Simple forward-auth provider to make cookie-based auth compatible with OpenID Connect

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors