# Traefik Simple-Auth

Traefik (opens new window) has become a popular choice for load-balancing docker applications.

WARNING

This is an example config, and doesn't have SSL enabled by default. Traefik supports SSL (opens new window). Make sure to enable it so that username and password are encrypted in transit!

In both cases, we use a same-domain cookie sharing technique, described here

# Forward Auth

This strategy is similar to nginx auth_request, where traefik will forward the request to simple-auth's vouch endpoint to see if a user has a session (in this case, stored in a cookie). Unlike nginx's auth_request, the user should be forwarded to simple-auth by the vouch endpoint if there is no valid session.

This can also be used via traefik to forward a header, eg the user id, to the downstream service.

graph LR A[Traefik] A -- ForwardAuth--> C[Simple-auth] A -- example.com --> B[App]

# docker-compose

version: '3.3'
services:

  # Traefik listening on port 88 (in case 80 conflicts with something...)
  traefik:
    image: traefik:v2.3
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:88"
    ports:
      - "88:88"
      - "8090:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  # Simpleauth
  simpleauth:
    image: zix99/simple-auth:latest
    environment:
      SA_WEB_LOGIN_COOKIE_JWT_SIGNINGKEY: a-unqiue-signing-key # CHANGE ME!!
      SA_VERBOSE: 'true'
      SA_WEB_LOGIN_SETTINGS_ROUTEONLOGIN: "http://${DOMAIN}:88"
      # Allow login to send user back to any subdomain
      SA_WEB_LOGIN_SETTINGS_ALLOWEDCONTINUEURLS: 'https?://.*${DOMAIN}(:\d+)?/.*'
      SA_WEB_LOGIN_COOKIE_DOMAIN: ${DOMAIN} # IMPORTANT: Higher-level domain
      SA_WEB_BASEURL: http://auth.${DOMAIN}:88
      SA_AUTHENTICATORS_VOUCH_ENABLED: 'true'
      # The vouch endpoint will put the user's UUID on this header to be forwarded
      SA_AUTHENTICATORS_VOUCH_USERHEADER: 'X-User-Id'
    volumes:
      - sadb:/var/lib/simple-auth
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.simpleauth.rule=Host(`auth.${DOMAIN}`)" # Fill in with your own domain
      - "traefik.http.routers.simpleauth.entrypoints=web"
      # Set up middleware, needs to be the internal URL of the service so that the continue URL is created correctly
      # Will be used by other services
      - "traefik.http.middlewares.simple-auth.forwardauth.address=http://simpleauth/api/v1/auth/vouch?forward=1"
      - "traefik.http.middlewares.simple-auth.forwardauth.authResponseHeaders=X-User-Id" # Forward this header to the end service
  
  # testapp is a small nodejs app that will verify your token and only let you in if you have a valid token
  testapp:
    image: nginx:latest
    labels:
      - "traefik.enable=true"
      # App settings
      - "traefik.http.routers.testapp.rule=Host(`${DOMAIN}`)" # Fill in with your own domain
      - "traefik.http.routers.testapp.entrypoints=web"
      # Attach to the simple-auth middleware defined in simple-auth
      - "traefik.http.routers.testapp.middlewares=simple-auth"
    

volumes:
  sadb: {}

This strategy uses same-domain cookie auth to authenticate the user inside the app, with no special load-balancer setup.

Here, we're using traefik to have both simple-auth and a testapp (validates the token in the cookie). The test-app will forward to auth.${DOMAIN} if it doesn't detect an auth token.

graph LR A[Traefik] A -- example.com --> B[Test App] A -- auth.example.com --> C[Simple-auth]

# docker-compose

version: '3.3'
services:

  # Traefik listening on port 88 (in case 80 conflicts with something...)
  traefik:
    image: traefik:v2.3
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:88"
    ports:
      - "88:88"
      - "8090:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  # Simpleauth
  simpleauth:
    image: zix99/simple-auth:latest
    environment:
      SA_WEB_LOGIN_COOKIE_JWT_SIGNINGKEY: a-unqiue-signing-key # CHANGE ME!!
      SA_VERBOSE: 'true'
      SA_WEB_LOGIN_SETTINGS_ROUTEONLOGIN: "http://${DOMAIN}:88"
      SA_WEB_LOGIN_COOKIE_DOMAIN: ${DOMAIN} # IMPORTANT: Higher-level domain
    volumes:
      - sadb:/var/lib/simple-auth
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.simpleauth.rule=Host(`auth.${DOMAIN}`)" # Fill in with your own domain
      - "traefik.http.routers.simpleauth.entrypoints=web"
  
  # testapp is a small nodejs app that will verify your token and only let you in if you have a valid token
  testapp:
    build: ./testapp
    environment:
      AUTHURL: "http://auth.${DOMAIN}:88"
      JWTKEY: a-unqiue-signing-key # This should match the signing key for simpleauth
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.testapp.rule=Host(`${DOMAIN}`)" # Fill in with your own domain
      - "traefik.http.routers.testapp.entrypoints=web"
    

volumes:
  sadb: {}

# Test App

TIP

You can find more information about the testapp here

This is a very simple nodejs app that will validate your auth cookie, or redirect you to the authentication portal if it fails.

You can see the full app at our repository

#!/usr/bin/env node
const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');

const PORT = process.env.PORT || 8080;
const AUTHURL = process.env.AUTHURL;
const JWTKEY = process.env.JWTKEY;

const app = express();

app.use(cookieParser());

// Simplistic auth middleware
app.use((req, res, next) => {
  const authCookie = req.cookies.auth;
  if (!authCookie) {
    // You could redirect here..
    return res.redirect(AUTHURL);
  }

  return jwt.verify(authCookie, JWTKEY, (err, decoded) => {
    if (err) {
      return res.status(401).send('Invalid token');
    }
    req.auth = decoded;
    return next();
  });
});

// Only can get if passes auth middleware
app.get('/', (req, res) => {
  res.send(`Hello!<br>
  Your auth cookie is: ${req.cookies.auth}<br>
  Your token decodes to: ${JSON.stringify(req.auth)}<br>
  <br>
  <a href="${AUTHURL}/#/manage">Click here to manage your account</a>`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://0.0.0.0:${PORT}`);
});