On the surface, running Single Sign On (SSO) in your home lab seems squarely in the realm of overkill. Unlike the corporate environments SSO tends to get deployed in, you’re probably not rotating in and out family members often (if you are, you need counseling and not an SSO solution). After running Authentik for some time, I’ve found a lot of practical use even in a homelab environment.

The SSO we’re talking about

SSO is a lot more common in larger corporate environments. There are a number of commercial services like Okta and EntraID, and the idea is you have a single source of truth for who should be able to sign in. Applications rely on this service to authenticate users rather than a database within each application. There are a lot of benefits in these larger environments; employees come and go and having one place to add or remove them improves security. For compliance, you can enforce things like multi factor authentication and centralize things so users only have one strong MFA device to worry about rather than 15 TOTP passcodes.

I’m focused mostly on web applications in my environments, so that’s what I’ll explain the most here. There are ways to integrate with both Windows and Linux systems, and with things like kerberos, signing into your machine can also authenticate you to any number of downstream services. I’m mostly focused on the “one username and password” SSO here, but know these other systems exist.

Why I started the migration

I ran into a problem that I’m sure many other homelabbers run into: once you let others start to use your services they need passwords and they forget the passwords. This can breed some pretty ugly security posture, in my case I was both storing all the user passwords in my password manager and sending them every time someone forgot one. Multiply this by a handful of services and users, and it became an enormous pain.

My primary goals were just to allow users to manage their own passwords and get them out of my password manager, so I started moving things to Authentik as my self hosted SSO solution.

As time has gone on, these are all the benefits I’ve seen with SSO in my lab:

  • One username, one password. Everyone only has one username and password to remember, myself included.
  • More cool two factor options. One thing the SSO at my day job has that I missed at home was push based two factor prompts. I went from a list of 15+ TOTP codes for things I had to dig through each login to an easy push notification. If that’s not your style, setting up passkeys or hardware security keys is easy as well.
  • Self managed password resets. If someone forgets their password, I set up outbound SMTP so they can reset their password via email. I don’t need to be involved anymore.
  • Securing apps that don’t support authentication. With some of the providers in Authentik, the underlying application doesn’t even need to know about or support authentication. Things like some status pages for internal services and read-only pages I can now put behind authentication.

Running Authentik

Authentik has some really good guides for self hosting, including a Helm chart I used to deploy the stack to my local Kubernetes stack. There’s a few pieces to the puzzle that you may or may not need when you set things up:

  • The server and worker: These are the main HTTP interface and worker process that handle things. You’ll always need these.
  • Outposts - These are protocol specific processes that you can bind applications to to allow things like Proxies and LDAP authentication to flow. On Kubernetes, Authentik will deploy and manage these itself automatically.

Following the relevant install guide should get you going, you’ll end up with a default akadmin user that will be your root administrator user for future management.

Some other things you’ll want to set up:

  • Set up SMTP so password reset emails will go through. I used SMTP2Go which has a free tier that for the very infrequent password reset emails is plenty.
  • I put my instance on a FQDN that works outside of my environment. None of my external users really have a VPN set up to get into my lab, so if they need a password reset or the like that’s just more friction I’m trying to avoid. You don’t have to expose services that use SSO to the internet if you go this route, my Paperless-NGX server is only accessible on my local network but uses SSO.
  • The branding options are a fun rabbit hole. In my case, I made a logo for my homelab, found a free landscape photo of my area for the background of the login pages, and adjusted the page titles to make it feel a bit more polished. None of this is really required, but it helps users confirm their in the right place and gives them cues about what they need to do next.
  • Set up rules to gate logins and password resets. Authentik supports reputations, fail2ban like scoring for repeated failed logins, and captchas that cut down on the bot noise for your authentication flows. If it’s out in the world, someone will try to brute force it but it’s easy to make most of those attacks impractical.
  • Once you set up apps, go back and add icons to them to make them look nice. This is purely aesthetic, but dashboardicons.com has a ton of app icons that you can use and it makes things easier to find visually.

Treat It Like Critical Infrastructure

If you set up SSO and point apps to it, it’s now a critical link in the chain for those apps to keep working. It needs some white glove treatment: keeping it updated and backed up for a start.

For all the services, make sure you can still access the service with a backup account even if the SSO is down. It may be down, but it may also be misconfigured and need some setting updating in the application itself. These “break-glass” accounts are sometimes mandated by the service itself, but if not make sure you have some fallback way to get into services.

Connecting Apps

Authentik on its own doesn’t do a ton, you’ll need to create applications for each application you want to run. There’s a ton of really good guides for specific apps in Authentik’s documentation, searching for “authentik appname” usually turns up the link with a guide on Authentik settings and app settings that need adjusted.

There’s a bit of architecture and terminology to understand through.

First you have Applications. These are easy, this is the configuration for a specific app in your environment you want to use SSO on. You’ll bind users, groups, or roles to these to give people access.

Each application has a provider. This is the actual authentication protocol used to authenticate users. There’s a few, and we’ll talk about OIDC and Proxy providers in a moment. The others I haven’t had to use yet, so I won’t talk about them here.

You also have Users, Groups, and Roles. These are self explanatory but they are found in the Directory section of the admin interface.

You also have Flows and Stages which are probably the most abstract. This is the process you set up for logging in, resetting a password, etc. A flow is the overall process, a stage is a step in that process. In general there isn’t much you’ll need to adjust, but some guides may need a specific flow (for example, LDAP) since it may not support all the normal stages. When you create an app, you’ll need to select an authentication flow for it to use.

You can also create apps without providers, and this is useful for links or things that you want to put in the user dashboard but don’t need a full SSO setup for. (Status pages, external links, etc)

OIDC Proivder

This is probably the most common, OpenID Connect is a popular way for apps to enable SSO and most of the homelab type apps support it out of the box. For this, you’ll have a client ID, client secret, and a URL set up inside Authentik that get passed to the specific app. The URL contains a standard set of configuration options to make it easy, and Authentik generates those based on the specific app and provider.

When a user loads the app or clicks to log in, they get a token and are sent to Authentik. They complete the authentication and are directed back into the app where the app can then take the token and ask Authentik if the user logged in. If all is well, the user is allowed into the app and things like their username, email, full name, and groups are passed in to the app.

Some gotcha’s I’ve run into:

  • There sometimes is some application specific settings that need changed in the provider. Some can be guessed or inferred, some need a guide to know. The Authentik specific guides are usually spot on.
  • Your app needs to know where the Authentik server is and have access. If it’s an offsite app and your instance isn’t public, for example, you’ll need some way for the app to make requests to Authentik to validate user tokens.

OIDC is so common now that if you run into issues, searching for OIDC and your app name will yield tons of results. In general it’s pretty easy to get going since Authentik does most of the work for you.

Proxy Provider

This one can be very simple but is also very powerful. There’s two ways I use it: disable authentication in the app (or use it on an unauthenticated app) to gate the app but everyone has one level fo access or some apps handle headers set in the proxy to authenticate based on the proxy.

On Authentik’s end of things, you’ll need to create a Proxy Outpost and deploy it, but you don’t need to forward any extra ports. Once you’ve created an outpost, create the app and provider and opt to secure an entire domain. Once created, you’ll need to go to the outpost and assign the provider to the outpost then wait for it to refresh the config (it does this every few minutes be default).

In the app, some apps support proxy authentication but I tend to use this on apps that either have no authentication or apps where everyone having global access is fine so I disable the authentication within the app. Keep in mind, our Nginx process that sits in front of the app will gate everything to keep it private.

For Nginx, this is the glue that makes it work. Here’s a sample reverse proxy config that forwards requests to an upstream app but also gates the entire app through Authentik:

# Force all traffic to HTTPS
server {
    listen 80 default_server;
    return 301 https://adsb.home.lab$request_uri;
}


# Proxy Config
server {
    listen 443 ssl default_server;
    server_name adsb.home.lab;

    # SSL Config (Ciphers are enforced globally now)
    ssl_certificate /etc/letsencrypt/live/adsb.home.lab/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/adsb.home.lab/privkey.pem;

    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    client_max_body_size 5M;

    # Blanket forward everything to our proxy.
    location / {
        
        ##############################
        # authentik-specific config
        ##############################
        auth_request     /outpost.goauthentik.io/auth/nginx;
        error_page       401 = @goauthentik_proxy_signin;

        auth_request_set $auth_user $upstream_http_x_authentik_username;
        auth_request_set $auth_email $upstream_http_x_authentik_email;
        auth_request_set $auth_groups $upstream_http_x_authentik_groups;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header       Set-Cookie $auth_cookie;

        proxy_set_header X-Authentik-Username $auth_user;
        proxy_set_header X-Authentik-Email $auth_email;
        proxy_set_header X-Authentik-Groups $auth_groups;
        

        proxy_pass http://127.0.0.1:8080;

        # Set inbound headers for the app
        proxy_set_header Range $http_range;
        proxy_set_header If-Range $http_if_range;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        

        # Next three lines allow websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Disable buffering on websockets
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
    
    # https://docs.goauthentik.io/add-secure-apps/providers/proxy/server_nginx/

    # all requests to /outpost.goauthentik.io must be accessible without authentication
    location /outpost.goauthentik.io {
        proxy_pass              https://auth.home.lab/outpost.goauthentik.io;
        proxy_ssl_server_name   on;
        proxy_ssl_name          auth.home.lab;
        proxy_ssl_verify        on;
        proxy_ssl_verify_depth  4;
        proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

        proxy_set_header        Host $host;
        proxy_set_header        X-Original-URL $scheme://$http_host$request_uri;
        add_header              Set-Cookie $auth_cookie;
        auth_request_set        $auth_cookie $upstream_http_set_cookie;
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
    }

    # Special location for when the /auth endpoint returns a 401,
    # redirect to the /start URL which initiates SSO
    location @goauthentik_proxy_signin {
        internal;
        add_header Set-Cookie $auth_cookie;
        return 302 /outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
        # For domain level, use the below error_page to redirect to your authentik server with the full redirect path
        # return 302 https://auth.home.lab/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
    }
    
}

The magic is mostly in the auth_request config option and the two location blocks. When a user loads the page, Nginx checks for auth by sending a request to the /outpost.goauthentik.io URL. That’s forwarded to the Authentik server and nginx understands a 200 is a sign to let them in, a 401 is a sign to send them into Authentik. The second location block handles the 401 to redirect them to start the signin process. The user stores a cookie that is their ticket, and as long as it’s present the request is allowed through.

A thing to note here too is that this setup restricts the entire app. If there’s a CVE or something with the app itself, the above config will shield any unauthenticated requests. For the majority of my simple apps, disabling auth and using this proxy is a huge advantage.

LDAP Provider

If something doesn’t speak OIDC and has users, there’s a good chance it speaks LDAP. LDAP is pretty limited, though, since the app takes the username and password and passes that directly to Authentik to validate. This can break things like MFA since Authentik can’t show you a page to prompt for a code or security key. If you can use OIDC or a proxy, go with that.

In my case, I use LDAP for Emby and it was probably the most nuanced setup. LDAP overall uses a bind user to login to LDAP as a known user, searches a configured area for users, then checks if the user trying to log in is in that area and they provided the correct password. Apps will call all of these settings different things, so find a guide for your app.

LDAP also requires an outpost, and you will need to forward the LDAP ports on your machine since LDAP will use its own protocol. You’ll also need an authentication flow that handles or disables two facto since LDAP just passes the username and password. (TOTP can sometimes be used if the user appends the TOTP passcode to their password, but I didn’t bother)

Other SSO Things To Know

  • Check if your app supports User Provisioning and adjust defaults. If a user has never logged into an app, often the app can set up defaults for the user and assign them some sensible roles. Some can be controlled by groups coming in via Authentik, but sometimes this needs enabled and configured by hand.
  • Always bind something to an app. By default, Authentik gives an unbound app to everyone. Binding a group for each app then adding users to that group keeps this from happening.
  • Don’t be a jerk with settings. SSO can be the worst part of the experience if you enforce things like excessive password complexity, forced password rotations, and the like. Set sensible defaults, enforce length requirements, but the rest don’t add much to your security posture and will just piss people off.
  • Tell your users if you’re moving. Their password may change, but mostly the new flow may be jarring if they aren’t expecting it. That’s partly why I set up some branding so they know they’re in the right place but a good user shouldn’t trust new redirects to strange sites when logging into things.