# OIDC authentication - Headscale
An open source, self-hosted implementation of the Tailscale control server.
Author: Headscale authors

Skip to content

# Configuring headscale to use OIDC authentication

In order to authenticate users through a centralized solution one must enable the OIDC integration.

Known limitations:

- No dynamic ACL support
- OIDC groups cannot be used in ACLs

## Basic configuration

In your `config.yaml`, customize this to your liking:

config.yaml

    oidc:
      # Block further startup until the OIDC provider is healthy and available
      only_start_if_oidc_is_available: true
      # Specified by your OIDC provider
      issuer: "https://your-oidc.issuer.com/path"
      # Specified/generated by your OIDC provider
      client_id: "your-oidc-client-id"
      client_secret: "your-oidc-client-secret"
      # alternatively, set `client_secret_path` to read the secret from the file.
      # It resolves environment variables, making integration to systemd's
      # `LoadCredential` straightforward:
      #client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
      # as third option, it's also possible to load the oidc secret from environment variables
      # set HEADSCALE_OIDC_CLIENT_SECRET to the required value

      # Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query
      # parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email".
      scope: ["openid", "profile", "email", "custom"]
      # Optional: Passed on to the browser login request – used to tweak behaviour for the OIDC provider
      extra_params:
        domain_hint: example.com

      # Optional: List allowed principal domains and/or users. If an authenticated user's domain is not in this list,
      # the authentication request will be rejected.
      allowed_domains:
        - example.com
      # Optional. Note that groups from Keycloak have a leading '/'.
      allowed_groups:
        - /headscale
      # Optional.
      allowed_users:
        - alice@example.com

      # Optional: PKCE (Proof Key for Code Exchange) configuration
      # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
      # by preventing authorization code interception attacks
      # See https://datatracker.ietf.org/doc/html/rfc7636
      pkce:
        # Enable or disable PKCE support (default: false)
        enabled: false
        # PKCE method to use:
        # - plain: Use plain code verifier
        # - S256: Use SHA256 hashed code verifier (default, recommended)
        method: S256

      # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
      # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
      # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
      # user: `first-name.last-name.example.com`
      strip_email_domain: true

## Azure AD example

In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:

terraform.hcl

    resource "azuread_application" "headscale" {
      display_name = "Headscale"

      sign_in_audience = "AzureADMyOrg"
      fallback_public_client_enabled = false

      required_resource_access {
        // Microsoft Graph
        resource_app_id = "00000003-0000-0000-c000-000000000000"

        resource_access {
          // scope: profile
          id   = "14dad69e-099b-42c9-810b-d002981feec1"
          type = "Scope"
        }
        resource_access {
          // scope: openid
          id   = "37f7f235-527c-4136-accd-4a02d197296e"
          type = "Scope"
        }
        resource_access {
          // scope: email
          id   = "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0"
          type = "Scope"
        }
      }
      web {
        # Points at your running headscale instance
        redirect_uris = ["https://headscale.example.com/oidc/callback"]

        implicit_grant {
          access_token_issuance_enabled = false
          id_token_issuance_enabled = true
        }
      }

      group_membership_claims = ["SecurityGroup"]
      optional_claims {
        # Expose group memberships
        id_token {
          name = "groups"
        }
      }
    }

    resource "azuread_application_password" "headscale-application-secret" {
      display_name          = "Headscale Server"
      application_object_id = azuread_application.headscale.object_id
    }

    resource "azuread_service_principal" "headscale" {
      application_id = azuread_application.headscale.application_id
    }

    resource "azuread_service_principal_password" "headscale" {
      service_principal_id = azuread_service_principal.headscale.id
      end_date_relative    = "44640h"
    }

    output "headscale_client_id" {
      value = azuread_application.headscale.application_id
    }

    output "headscale_client_secret" {
      value = azuread_application_password.headscale-application-secret.value
    }

And in your headscale `config.yaml`:

config.yaml

    oidc:
      issuer: "https://login.microsoftonline.com/<tenant-UUID>/v2.0"
      client_id: "<client-id-from-terraform>"
      client_secret: "<client-secret-from-terraform>"

      # Optional: add "groups"
      scope: ["openid", "profile", "email"]
      extra_params:
        # Use your own domain, associated with Azure AD
        domain_hint: example.com
        # Optional: Force the Azure AD account picker
        prompt: select_account

## Google OAuth Example

In order to integrate headscale with Google, you'll need to have a Google Cloud Console account.

Google OAuth has a verification process if you need to have users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie `@example.com`), you don't need to go through the verification process.

However if you don't have a domain, or need to add users outside of your domain, you can manually add emails via Google Console.

### Steps

1. Go to Google Console and login or create an account if you don't have one.
2. Create a project (if you don't already have one).
3. On the left hand menu, go to `APIs and services` -> `Credentials`
4. Click `Create Credentials` -> `OAuth client ID`
5. Under `Application Type`, choose `Web Application`
6. For `Name`, enter whatever you like
7. Under `Authorised redirect URIs`, use `https://example.com/oidc/callback`, replacing example.com with your headscale URL.
8. Click `Save` at the bottom of the form
9. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
10. Edit your headscale config, under `oidc`, filling in your `client_id` and `client_secret`:
    config.yaml

        oidc:
          issuer: "https://accounts.google.com"
          client_id: ""
          client_secret: ""
          scope: ["openid", "profile", "email"]

You can also use `allowed_domains` and `allowed_users` to restrict the users who can authenticate.