Skip to main content

Self-Hosted IAM

Self-hosting your own applications & services is both a great way to learn and a good way to reduce your reliance on 'cloud services'.
However it comes with both significant risk due to bad configuration, duplication of work due to many self-hosted apps having their own authentication methods, and a lack of ability to secure applications that do not have authentication at all - like a lot of OSS software.

This guide will go over a basic configuration for a combination of OSS projects that can help solve all of the above issues.

What is IAM?

IAM stands for 'Identity and Access Management', and refers to a solution that consolidates Identity Services and Access Management into one coherent platform for usage across applications.
The IAM solution can be as 'simple' as providing the frontend components, or a fully-featured, enterprise grade solution, with everything baked in, like Microsoft Azure Active Directory.

Pre-Req's

  1. A container host, either Docker hosted on something, or Unraid.
  2. A setup & configured Nginx Proxy Manager instance.
  3. (Ideally) the ability to create a DNS record on your domain, pointed at NPM. a. Example - auth.example.com

Overview

The idea here is that we will deploy an authentication portal and it's required components to allow you to share login credentials across all of your apps, thus securing them and simplifying their management.

What we'll be using

Authelia

Authelia describes it self as such -

Authelia is an open-source authentication and authorization server and portal fulfilling the identity and access management (IAM) role of information security in providing multi-factor authentication and single sign-on (SSO) for your applications via a web portal. It acts as a companion for common reverse proxies.

I would personally describe it as a fantastic piece of IAM software for self-hosters, that has great support and is fast and lightweight to boot.

Required Components -
  • MariaDB
    MariaDB will be used to act as Authelia's storage backend. It stores user preferences, 2FA device handles, secrets, logs, etc.
    Authelia supports several, but for this deployment we will be using MariaDb.
  • Redis
    Redis will be used to cache the session cookies that Authelia uses to memory, thus speeding up the login process.
  • LLDAP
    lldap, or LightLDAP, is a very lightweight, simplistic LDAP backend that can be used directly as a basic LDAP server for many application, or in combination with an IAM solution like Authelia to help provide a centralized 'database' of users and attributes.
Useful Tools

Hosting Options

We have two options that we will be going over here, Unraid & Docker-Compose.
One is a little more involved than the other (Unraid), however more prevalent in the self-hosting community.

Whereas Docker-Compose is a much easier deployment overall, compatible with many methods of deployment, but is not supported by Unraid out of the box.

Docker & Docker Compose

Copy/paste the below code block into your text editor of choice, edit the variables listed underneath, then run docker-compose to deploy -

docker-compose -up

version: '1'
services:
# LightLDAP
lightldap:
container_name: lightldap
hostname: lightldap
image: nitnelave/lldap
restart: unless-stopped
environment:
- LLDAP_JWT_SECRET='my-jwt-secret'
- LLDAP_LDAP_USER_PASS='my-secret-password'
- LLDAP_LDAP_BASE_DN=dc=example,dc=com
ports:
- "3890:3890" # LDAP
networks:
- proxynet
volumes:
- lightldap_appdata:/data

# redis_authelia
redis_authelia:
container_name: redis_authelia
hostname: redis_authelia
image: bitnami/redis:latest
restart: unless-stopped
environment:
- ALLOW_EMPTY_PASSWORD=no
- REDIS_PASSWORD='my-redis-password'
networks:
- proxynet

# mariadb_authelia
mariadb_authelia:
container_name: mariadb_authelia
hostname: mariadb_authelia
image: lscr.io/linuxserver/mariadb
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD='mysql-root-password'
- MYSQL_DATABASE=authelia
- MYSQL_USER=authelia
- MYSQL_PASSWORD='authelia-database-password'
networks:
- proxynet
volumes:
- mariadb_authelia_appdata:/config

# authelia
authelia:
container_name: authelia
hostname: authelia
image: authelia/authelia
restart: unless-stopped
networks:
- proxynet
volumes:
- authelia_appdata:/config

# Networks
networks:
proxynet:
name: proxynet

# Volumes
volumes:
mariadb_authelia_appdata:
driver: local
lightldap_appdata:
driver: local
authelia_appdata:
driver: local

Unraid

I wont be going too in-depth on this section as its covered elsewhere, so i'll just list the settings you need to input and containers to find in the 'app store'.

Creating a general usage docker network.

Drop to the Unraid CLI, either via the WebGUI (top right), or over SSH, and run the following -

docker network create proxynet --subnet=172.20.0.0/16 --gateway=172.20.0.1

This will create a custom docker network that you will be using across your applications, behind NPM.

note

If you've already created a custom docker network, you can skip this section and just populate the variables below with the name of your custom network.

info

Container Port Usage
All of these containers will be on the same container network, with their 'frontend', Authelia, only presented through Nginx Proxy Manager.
As such, there is no need for them to have host port connection specified, along with unnecessary port usage.

For each of the containers, as you deploy their templates, use the REMOVE button on the variable for the ports on each.

MariaDB

This is listed in the appstore, search for 'mariadb', but don't get the container with the 'official' tag. Instead get the one labeled as being from 'linuxserver's Repository'.

Setting NameVariableNote
Namemariadb_autheliaThis can be whatever you want, but assuming multiple MariaDB containers in future, its good to have naming consistency
Appdata/mnt/user/appdata/mariadb_autheliaThis can be whatever you want, but similar to the above, its good to have consistency, and i recommend setting it to the same as the container name
NetworkproxynetYour custom docker network
MYSQL_ROOT_PASSWORDmy-secret-root-passwordGenerate a password
MYSQL_DATABASEautheliathis can be whatever you want, but i recommend something descriptive
MYSQL_USERautheliathis can be whatever you want, but i recommend something descriptive
MYSQL_PASSWORDmy-secret-passwordGenerate a password
REMOTE_SQLremove the defaultsn/a

Redis

This is listed in the appstore, search for 'mariadb', but don't get the container with the 'official' tag. Instead get the one labeled as being from 'A75G's Repository'.

Setting NameVariableNote
Nameredis_autheliaThis can be whatever you want, but assuming multiple redis containers in future, its good to have naming consistency
Appdata/mnt/user/appdata/redis_autheliaThis can be whatever you want, but similar to the above, its good to have consistency, and i recommend setting it to the same as the container name
NetworkproxynetYour custom docker network
ALLOW_EMPTY_PASSWORDnoSpecifies whether to allow anonymous redis access
Passwordmy-secret-passwordGenerate a password

LightLDAP

This is not listed in the appstore, so instead we will need to create a custom container, with a repository of "nitnelave/lldap".

Setting NameVariableNote
NameLightLDAPThis can be whatever you want, but its probably best to name it after the application. ;-)
Data/mnt/user/appdata/LightLDAPThis can be whatever you want, but similar to the above, its good to have consistency, and i recommend setting it to the same as the container name
NetworkproxynetYour custom docker network
LLDAP_JWT_SECRETmy-jwt-secretGenerate a JWT Secret, 512bit
LLDAP_LDAP_USER_PASSmy-secret-passwordGenerate a password
LLDAP_LDAP_BASE_DNdc=example,dc=comThe domain name for your LDAP, in common naming format
tip

Once up and running, i recommend creating a NPM proxy host for the service, and restricting it to your local LAN only, using Access Lists to specify your local subnet.

Now LLDAP is operational, browse to its web interface, login with admin/admin, change the admin password, then create two extra users -

  1. A general user for yourself - my.name.
  2. A service user for Authelia.
    Once created, add the Authelia user to the group lldap_password_manager in the Groups tab.

This will give you both a user to use day-to-day for logging into your services, and a user that has relevant permissions for Authelia to do password resets.

Why aren't we using "official" container templates?

Several reasons - In most cases the application template in use from the Unraid AppStore is just a template of environment variables and defaults that still uses the official repository, and/or is from a repository from one of the 'big two', Bitnami or LSIO, which is useful for reducing the total size of your image usage across your apps, as using the same images from the same source ensures that there will be common image layers, reducing the storage used, decreasing time to update and ultimately being from image packagers who are well known for being reliable.

Authelia

Container Configuration

Setting NameVariableNote
NameAutheliaThis can be whatever you want, but its probably best to name it after the application. ;-)
AppData Config Path/mnt/user/appdata/AutheliaThis can be whatever you want, but similar to the above, its good to have consistency, and i recommend setting it to the same as the container name
NetworkproxynetYour custom docker network
tip

Don't start the Authelia container yet, we still need to create a config file.

Config file

In the appdata location above (/mnt/user/appdata/Authelia), create a file called "configuration.yml", and copy/paste the below code block into it.
The code block itself will help setup the basics of Authelia for you.

note

Lines that need editing are labeled ## EDIT.

# yamllint disable rule:comments-indentation
---
###############################################################################
# Base Configuration #
###############################################################################

theme: dark #light/dark
jwt_secret: my-jwt-secret-for-authelia # EDIT - add a generated JWT secret here

default_redirection_url: https://www.google.com/ #where to redirect for a non-existent URL

# Server base settings
server:
host: 0.0.0.0
port: 9091
path: ""
buffers:
read: 4096
write: 4096
enable_pprof: false
enable_expvars: false
disable_healthcheck: false
tls:
key: ""
certificate: ""

# Logging verbosity level
log:
## level: info
level: warn ## info/warn/debug
format: text

# TOTP Settings
totp:
issuer: example.com ## EDIT - your top-level domain
period: 30
skew: 1

###############################################################################
# Authentication #
###############################################################################


# Authentication Backend
authentication_backend:
password_reset:
disable: false
refresh_interval: 5m

## LDAP
ldap:
## LightLDAP
implementation: custom
url: ldap://LightLDAP:3890
timeout: 5s
start_tls: false
base_dn: dc=example,dc=com ## EDIT - add your base DN from the previous step
username_attribute: uid
additional_users_dn: ou=people
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
additional_groups_dn: ou=groups
groups_filter: (member={dn})
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: uid=authelia,ou=people,dc=example,dc=com ## EDIT - add your base DN from the previous step
password: "my-authelia-password" ## EDIT - add your Authelia user password from the lldap setup

###############################################################################
# Access Control #
###############################################################################

# Access Control
access_control:
default_policy: deny

## Network Definitions
networks:
- name: internal
networks:
- 192.168.0.0/24 ## EDIT - add/edit the your local network ranges
- name: vpn
networks:
- 10.6.0.0/24 ## EDIT - add/edit the network ranges for your VPN (if you have one)

## Domain Rules
rules:
## External Bypass
- domain:
- "auth.example.com" ## EDIT - Authelia subdomain
- "example.com" ## EDIT - your root domain
- "www.example.com" ## EDIT - your root www domain
policy: bypass

## Multi-Factor Auth
- domain:
- "*.example.com"
policy: two_factor

###############################################################################
# Sessions & Caching Configuration #
###############################################################################

# Sessions
session:
name: authelia_session
secret: my-session-secret # EDIT - your session secret
expiration: 72h # 72 hour
inactivity: 24h # 24 hours
remember_me_duration: 7d # 7 days
domain: example.com # Your root domain

# Redis Caching Configuration
redis:
host: redis_authelia
port: 6379
password: my-redis-password # EDIT - your redis password
database_index: 0
maximum_active_connections: 8
minimum_idle_connections: 0

# Regulations
regulation:
max_retries: 5
find_time: 10m
ban_time: 12h

storage:
# local:
encryption_key: my-encryption-key # EDIT - your encryption key

###############################################################################
# Database #
###############################################################################

## MySQL / MariaDB (Storage Provider)
mysql:
host: mariadb_authelia
port: 3306
database: authelia
username: authelia
password: my-authelia-database-password # EDIT - your database password
timeout: 5s

Next we need to modify the following items in the code block above -

Configuration VariableWhat to enter
jwt_secretA random alphanumeric string, you can generate on your CLI with the code listed here
totp:issuerYour top level domain
ldap:base_dnA modified base dn, with your domain entered
ldap:userYour LDAP service account username
ldap:passwordYour LDAP service account password
access_control:networks:name:internalYour internal network subnet
access_control:networks:name:vpnYour VPN network subnet
access_control:rules:domainReplace all 'example.com's' with your root domain
session:domainYour root domain
session:redis:passwordYour redis password
storage:encryption_keyA random alphanumeric string, you can generate on your CLI with the code listed here
storage:mysql:passwordYour MariaDB password

Nginx Proxy Manager

At this point, we effectively have Authelia operational, however we now need to ensure that traffic for your sub-domains are redirected to Authelia for authentication.
First, we need to add a new proxy host for your Authelia install.

Browse to your NPM web-GUI and create a new host with the following top-level settings, adjusting example.com and email as needed. -

SettingSet To
Details -> Domain Namesauth.example.com
Details -> Schemehttp
Details -> Forward NameAuthelia
Details -> Forward Port9091
Details -> Cache AssetsOff
Details -> Block Common ExploitsOn
Details -> Websockets SupportOn
Details -> Access ListPublicly Accessible
Custom LocationsBlank/no-settings on page
SSL -> SSL CertificateRequest a new SSL Certificate
SSL -> Force SSLOn
SSL -> HTTP/2 SupportOn
SSL -> HSTS EnabledOn
SSL -> HSTS subdomainsOn
SSL -> Use a DNS challengeNo
SSL -> Email address for Lets EncryptYour email address
SSL -> I AgreeOn

On the advanced tab, copy/paste the following code block -

note

Note the config changes needed near the bottom of the code block, and adjust as needed.

location / {
set $upstream_authelia http://Authelia:9091;
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;

# Remove comment from "CF-Connecting-IP", and comment out "X-Forwarded-For" if you are using Cloudflare tunnels
#real_ip_header CF-Connecting-IP; .
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# Adjust subnets to your internal subnets
set_real_ip_from 192.168.0.0/24;
set_real_ip_from 10.6.0.0/24;

# Uncomment all of the below if using Cloudflare Tunnels
#set_real_ip_from 103.21.244.0/22;
#set_real_ip_from 103.22.200.0/22;
#set_real_ip_from 103.31.4.0/22;
#set_real_ip_from 104.16.0.0/13;
#set_real_ip_from 104.24.0.0/14;
#set_real_ip_from 108.162.192.0/18;
#set_real_ip_from 131.0.72.0/22;
#set_real_ip_from 141.101.64.0/18;
#set_real_ip_from 162.158.0.0/15;
#set_real_ip_from 172.64.0.0/13;
#set_real_ip_from 173.245.48.0/20;
#set_real_ip_from 188.114.96.0/20;
#set_real_ip_from 190.93.240.0/20;
#set_real_ip_from 197.234.240.0/22;
#set_real_ip_from 198.41.128.0/17;
#set_real_ip_from 2400:cb00::/32;
#set_real_ip_from 2606:4700::/32;
#set_real_ip_from 2803:f800::/32;
#set_real_ip_from 2405:b500::/32;
#set_real_ip_from 2405:8100::/32;
#set_real_ip_from 2c0f:f248::/32;
#set_real_ip_from 2a06:98c0::/29;
}

Next, we need to setup the redirects on your subdomains. To do this, in Nginx Proxy Manager web-GUI, edit an existing host you have created, changing to the 'advanced' tab.
In here, copy/paste the below code block, changing two sets of variables -

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

Change the EDIT on these two lines to a unique name. I would recommend the name of your container/app.

set_real_ip_from 192.168.0.0/24;
set_real_ip_from 10.6.0.0/24;

Change these two to your local subnet and your VPN subnet (again, if you have one).

ssl_stapling on;
ssl_stapling_verify on;

location /authelia {
internal;
set $upstream_authelia http://Authelia: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_EDIT $forward_scheme://$server:$port;
proxy_pass $upstream_EDIT;

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.boomam.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_set_header X-webobjects-remote-user $user;
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 192.168.0.0/24;
set_real_ip_from 10.6.0.0/24;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;

}

Testing

You can now start the Authelia container.
After it does its initial start and builds the database, it should load in under 10-15 seconds in future.
Now, you can test that Authelia works by browsing to your auth domain (https://auth.example.com), and then assuming that works, testing your application subdomains ask you for Authelia auth.

Assuming it works, it will ask for your user login (using the login/s you set in LLDAP) and begin TOTP setup.

At this point, you have a basic working installation.

Advanced Authelia Configuration

NTP

NTP is used within Authelia to help keep the TOTP tokens in sync.
The default NTP settings are to take from the host, however this can cause issues if your host has any outages, causing a drift in the TOTP code timings. If you use your own NTP setup (another container, device on your network, etc.) you can specify it within the config with the code block below, just copy/pasting it to the bottom of configuration.yml

###############################################################################
# NTP #
###############################################################################
ntp:
address: "1.2.3.4:123"
version: 3
max_desync: 5s
disable_startup_check: false
disable_failure: false

OIDC (OpenID)

OpenID Connect can be used with Authelia to allow other apps to SSO (Single Sign On) against Authelia, using a common directory (like LDAP).
At a basic level, we will need to specify the 'basics' of the configuration with the code block below, which will set a few global OIDC settings, and your private-keys in use.

You will need to generate the following information, and enter into the code block below -

hmac_secret

From a command line, run -

LENGTH=64
tr -cd '[:alnum:]' < /dev/urandom | fold -w "${LENGTH}" | head -n 1 | tr -d '\n' ; echo

Copy/paste the result into the code block below.

RSA Private Key

From a command line, run -

openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

Copy/paste the result into the code block below.

###############################################################################
# OpenID Connect (OIDC) #
###############################################################################
identity_providers:
oidc:
hmac_secret: my-hmac-secret
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
my-rsa-private-key
-----END RSA 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_from_client_redirect_uris: false

Next, we need to configure our clients.
In this context 'clients' are the services that will use OIDC connect. So for example, NextCloud, or Grafana.
You specify each client underneath clients: with an - id: block for each client.
Below are example configurations, however there is extensive documentation on common app configurations for this on the Authelia documentation site.

    clients:
- id: grafana
secret: my-grafana-oidc-secret
public: false
authorization_policy: two_factor
pre_configured_consent_duration: 7d
scopes:
- openid
- profile
- groups
- email
redirect_uris:
- https://grafana.example.com/login/generic_oauth
userinfo_signing_algorithm: none

- id: nextcloud
secret: my-nextcloud-oidc-secret
public: false
authorization_policy: two_factor
scopes:
- openid
- profile
- email
redirect_uris:
- https://nextcloud.example.com/oidc/callback
userinfo_signing_algorithm: none

Notifications

Authelia can be configured to allow the sending of notifications to various services, commonly SMTP (email) services. Below is an example code block you can use to configure with gmail.
More information can be found here.

tip

I highly recommend the usage of 'app passwords' if you use Gmail or Outlook for this.
Even better would be to use Sendgrid and an API password.

###############################################################################
# Notifications, Email #
###############################################################################

notifier:
disable_startup_check: true #true/false
smtp:
# SendGrid
username: my-google-username
password: my-google-app-password
host: smtp.gmail.com
port: 465
sender: [email protected]
identifier: localhost
subject: "[Authelia] {title}"
startup_check_address: [email protected]
disable_require_tls: false
disable_html_emails: false
tls:
skip_verify: false
minimum_version: TLS1.2

API Bypasses

Several applications (NextCloud, Home Assistant, BitWarden, etc.) all have mobile applications that do no understand how to interface with their respective servers through an application like Authelia.
To bypass this issue, we can add a code block to the Access Control section of the main configuration, underneath rules:, making sure its specified above the general *.example.com catch-all, and the other bypass rule.
The example code below shows generalized paths, and paths for Home Assistant, NextCloud & BitWarden.
You can remove these line-by-line if you don't need them.

    ## API Bypass
- domain:
- "nextcloud.example.com"
- "bitwarden.example.com"
- "homeassistant.example.com"
resources:
- "^/api.*" ## General
- "^/api/v1.*" ## General
- "^/api/firstfactor.*" ## General
- "/api/*" ## General
- "^/auth/token.*" ## HomeAssistant
- "^/.external_auth=.*" ## HomeAssistant
- "^/service_worker.js" ## HomeAssistant
- "^/static.*" ## HomeAssistant
- "^/local.*" ## HomeAssistant
- "^/hacsfiles.*" ## HomeAssistant
- "^/frontend_latest.*" ## HomeAssistant
- "^/apps/theming/.*" ## NextCloud
- "^/apps/groupfolders/.*" ## NextCloud
- "^/apps/side_menu/.*" ## NextCloud
- "^/apps/user_status/.*" ## NextCloud
- "^/core/img/.*" ## NextCloud
- "^/core/svg/.*" ## NextCloud
- "^/core/css/.*" ## NextCloud
- "^/core/ocs/.*" ## NextCloud
- "^/core/js/.*" ## NextCloud
- "^/core/l10n/.*" ## NextCloud
- "^/s/.*" ## NextCloud
- "^/remote.php/dav.*" ## NextCloud
- "^/apps/files_sharing/*" ## NextCloud
- "^/ocs/v1.php/.*" ## NextCloud
- "^/ocs/v2.php/.*" ## NextCloud
- "^/apps/text/js/text-public.js" ## NextCloud
- "^/apps/text/l10n/.*" ## NextCloud
- "^/css/core/.*" ## NextCloud
- "^/css/files_sharing/.*" ## NextCloud
- "^/css/icons/icons-vars.css" ## NextCloud
- "^/css/text/.*" ## NextCloud
- "^/dist/core-common.js" ## NextCloud
- "^/dist/core-files_client.js" ## NextCloud
- "^/dist/core-files_fileinfo.js" ## NextCloud
- "^/dist/core-main.js" ## NextCloud
- "^/dist/files_sharing-main.js" ## NextCloud
- "^/dist/merged-template-prepend.js" ## NextCloud
- "^/js/core/merged-template-prepend.js" ## NextCloud
- "^/svg/core/logo/logo/.*" ## NextCloud
- "^/api/verify" ## NextCloud
- "^/identity/connect/*" ## BitWarden
- "^/notifications/*" ## BitWarden
- "^/duo.*" ## BitWarden
- "^/favicon.*" ## BitWarden
- "^/images/*" ## BitWarden
- "^/icons/*" ## BitWarden
policy: bypass

Conclusion

Assuming everything has worked as intended, you now have fully protected, self-hosted, set of applications, secured behind a robust IAM, that gives you the ability to enable SSO for apps that support OIDC.

Further Reading