Skip to main content

SSO Details

The SSO is handled by Authelia, and a lot of my configs are based on this page. It was pretty difficult to get this going, but now that it is I will give an overview of how it works. It is integrated with the proxy server so that when SSO protected domains are requested my proxy server hands the request off to Authelia for authentication. Authelia sets a cookie when you first log in, so if you've already logged in somewhere, it will just pass you through to the website. I also was able to cobble together OIDC integration with Authelia, and that is integrated with my audiobook site as well as this book stack site. 

Here's a diagram of how the proxy, SSO, and containers interact. The site items with ovals are the docker containers with open ports that the proxy server would normally forward unfettered. On sites that need SSO, I add a big pile of code to the custom part of the NPM site config that tells it to finish up with Authelia. Then, Authelia does it's stuff based on the config file. The nope in the oval is the forbidden page that you see when you are logged into SSO and try to access a site that Authelia says nope to. 

Here is Authelia's diagram of how they do stuff

image.png

Here is my diagram of how it works on Matt-Cloud.

image.png

For the terribly curious, here is a stripped out docker-compose.yaml and configuration.yml for authelia and the nginx configs.

docker-compose.yaml
services:

  authelia:
    container_name: authelia
    image: 'authelia/authelia'
    restart: always
    network_mode: bridge
    ports:
      - 9091:9091
    volumes:
      - ./authelia-config:/config
      - ./authelia-secrets:/secrets

  authelia-redis:
    container_name: authelia-redis
    image: bitnami/redis:latest
    volumes:
      - ./authelia-redis:/bitnami/
    environment:
      REDIS_PASSWORD: "lolwtfbbqlolwtfbbq"
    restart: always
    network_mode: bridge
    ports:
      - 6379:6379

  authelia-db:
    image: postgres
    container_name: authelia-db
    restart: always
    network_mode: bridge
    volumes:
      - ./authelia-db:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: authelia
      POSTGRES_USER: authelia
      POSTGRES_PASSWORD: lolwtfbbqlolwtfbbq
    ports:
      - 5432:5432

 

configuration.yml
---
###############################################################################
#                           Authelia Configuration                            #
###############################################################################

theme: dark
jwt_secret: "lolwtfbbqlolwtfbbq"
default_redirection_url: https://auth.domain.com/

server:
  host: 0.0.0.0
  port: 9091
  disable_healthcheck: false

log:
  level: info

totp:
  disable: false
  issuer: 'auth.domain.com'
  algorithm: 'sha1'
  digits: 6
  period: 30
  skew: 1
  secret_size: 32
  allowed_algorithms:
    - 'SHA1'
  allowed_digits:
    - 6
  allowed_periods:
    - 30
  disable_reuse_security_policy: false

authentication_backend:
  ldap:
    address: 'ldap://pdc.domain.local:389'
    implementation: 'activedirectory'
    base_dn: 'OU=users,DC=domain,DC=local'
    users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))
    groups_filter: (&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))
    group_name_attribute: cn
    mail_attribute: mail
    display_name_attribute: displayname
    user: 'CN=LDAP Service,OU=Service,OU=users,DC=domain,DC=local'
    password: 'lolwtfbbq'
  disable_reset_password: true

access_control:
  ## just some simple example rules
  default_policy: deny
  rules:
    ## bypass rule
    - domain:
        - "auth.domain.com"
      policy: bypass
    - domain:
        - "*.domain.com"
      resources:
        - "^/api([/?].*)?$"
      policy: bypass
    ## 2fa domain
    - domain:
        - "2fa.domain.com"
      policy: two_factor
      subject:
        - "group:2FA-Users"
    # Normal protection
    - domain:
        - "secure.domain.com"
      policy: one_factor
      subject: "group:secure-users"

session:
  name: authelia_session
  domain: domain.com
  same_site: lax
  secret: "lolwtfbbqlolwtfbbq"
  expiration: 30d
  inactivity: 1d
  remember_me_duration: 6M
  redis:
    host: 0.0.0.0
    port: 6379
    password: "lolwtfbbqlolwtfbbq"
    database_index: 0
    maximum_active_connections: 10
    minimum_idle_connections: 0


regulation:
  max_retries: 3
  find_time: 10m
  ban_time: 12h


storage:
  encryption_key: 'lolwtfbbqlolwtfbbq'
  postgres:
    address: 'tcp://0.0.0.0:5432'
    database: 'authelia'
    schema: 'public'
    username: 'authelia'
    password: 'lolwtfbbqlolwtfbbq'


notifier:
  disable_startup_check: false
  smtp:
    username: authelia@domain.com
    password: "lolwtfbbqlolwtfbbq"
    host: mail.domain.com
    port: 587
    sender: authelia@domain.com
    identifier: authelia.domain.local
    subject: "[Authelia] {title}"
    startup_check_address: user@domain.com
    disable_require_tls: false
    disable_html_emails: true
    tls:
      skip_verify: false
      minimum_version: TLS1.2


identity_providers:
  oidc:
    hmac_secret: lolwtfbbqlolwtfbbq # provide secure secret
    issuer_certificate_chain: |
      -----BEGIN CERTIFICATE-----
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      -----END CERTIFICATE-----
    issuer_private_key: |
      -----BEGIN PRIVATE KEY-----
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      lolwtfbbqlolwtfbbq
      -----END PRIVATE KEY-----
    access_token_lifespan: 1h
    authorize_code_lifespan: 1m
    id_token_lifespan: 1h
    refresh_token_lifespan: 90m
    enable_client_debug_messages: false
    enforce_pkce: public_clients_only
    cors:
      endpoints:
        - authorization
        - token
        - revocation
        - introspection
      allowed_origins:
        - https://*.domain.com # adjust to your url
      allowed_origins_from_client_redirect_uris: false
    clients:
      - id: domain-oidc
        description: domain
        secret: 'lolwtfbbqlolwtfbbq' # provide secure secret
        sector_identifier: 'auth.domain.com'
        public: false
        authorization_policy: one_factor # may use two_factor to enforce 2FA
        consent_mode: implicit
        pre_configured_consent_duration: 6m
        audience: []
        scopes:
          - openid
          - groups
          - email
          - profile
        redirect_uris: # adjust to your domains
          - https://auth.domain.com/
          - https://auth.domain.com/oauth2/callback
          - https://audiobooks.domain.com/oauth2/callback
          - https://audiobooks.domain.com/auth/login
          - https://audiobooks.domain.com/user-settings
          - https://audiobooks.domain.com
          - https://audiobooks.domain.com/auth/openid/callback
        grant_types:
          - refresh_token
          - authorization_code
          - implicit
        response_types:
          - code
          - id_token
        response_modes:
          - form_post
          - query
          - fragment
        userinfo_signing_algorithm: none
nginx-server
location / {
set $upstream_authelia http://172.17.0.1:9091; # This example assumes a Docker deployment 
proxy_pass $upstream_authelia;
client_body_buffer_size 128k;

#Timeout if the real server is dead
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;

# Advanced Proxy Config
send_timeout 5m;
proxy_read_timeout 360;
proxy_send_timeout 360;
proxy_connect_timeout 360;

# Basic Proxy Config
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect  http://  $scheme://;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 64 256k;

# If behind a reverse proxy, forwards the correct IP, assumes you're using Cloudflare. Adjust IP for your Docker network.
set_real_ip_from 172.17.0.0/16;
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
}
nginx-client
location /authelia {
internal;
set $upstream_authelia http://0.0.0.0:9091/api/verify;
proxy_pass_request_body off;
proxy_pass $upstream_authelia;    
proxy_set_header Content-Length "";

# Timeout if the real server is dead
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
client_body_buffer_size 128k;
proxy_set_header Host $host;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr; 
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect  http://  $scheme://;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 4 32k;

send_timeout 5m;
proxy_read_timeout 240;
proxy_send_timeout 240;
proxy_connect_timeout 240;
}

location / {
set $upstream_app $forward_scheme://$server:$port;
proxy_pass $upstream_app;

auth_request /authelia;
auth_request_set $target_url https://$http_host$request_uri;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $email $upstream_http_remote_email;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Email $email;
proxy_set_header Remote-Groups $groups;

error_page 401 =302 https://auth.domain.com/?rd=$target_url;

client_body_buffer_size 128k;

proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;

send_timeout 5m;
proxy_read_timeout 360;
proxy_send_timeout 360;
proxy_connect_timeout 360;

proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect  http://  $scheme://;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 64 256k;

set_real_ip_from 172.17.0.0/16;
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

}