# 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: ```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" # 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 # By default, Headscale will use the OIDC email address claim to determine the username. # OIDC also returns a `preferred_username` claim. # # If `use_username_claim` is set to `true`, then the `preferred_username` claim will # be used instead to set the Headscale username. # If `use_username_claim` is set to `false`, then the `email` claim will be used # to derive the Headscale username (as modified by the `strip_email_domain` entry). use_username_claim: false # 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: ```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`: ```yaml oidc: issuer: "https://login.microsoftonline.com//v2.0" client_id: "" client_secret: "" # 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](https://console.cloud.google.com) account. Google OAuth has a [verification process](https://support.google.com/cloud/answer/9110914?hl=en) 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](https://console.cloud.google.com) 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`: ```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. ## Authelia Example In order to integrate Headscale with your Authelia instance, you need to generate a client secret add your Headscale instance as a client. First, generate a client secret. If you are running Authelia inside docker, prepend `docker-compose exec ` before these commands: ```shell authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 ``` This will return two strings, a "Random Password" which you will fill into Headscale, and a "Digest" you will fill into Authelia. In your Authelia configuration, add Headscale under the client section: ```yaml clients: - id: headscale description: Headscale secret: "DIGEST_STRING_FROM_ABOVE" public: false authorization_policy: two_factor redirect_uris: - https://your.headscale.domain/oidc/callback scopes: - openid - profile - email - groups ``` In your Headscale `config.yaml`, edit the config under `oidc`, filling in the `client_id` to match the `id` line in the Authelia config and filling in `client_secret` from the "Random Password" output. You may want to tune the `expiry`, `only_start_if_oidc_available`, and other entries. The following are only the required entries. ```yaml oidc: issuer: "https://your.authelia.domain" client_id: "headscale" client_secret: "RANDOM_PASSWORD_STRING_FROM_ABOVE" scope: ["openid", "profile", "email", "groups"] allowed_groups: - authelia_groups_you_want_to_limit ``` In particular, you may want to set `use_username_claim: true` to use Authelia's `preferred_username` grant to set Headscale usernames.