<!doctype html><htmllang=enclass=no-js><head><metacharset=utf-8><metaname=viewportcontent="width=device-width,initial-scale=1"><metaname=descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaname=authorcontent="Headscale authors"><linkhref=https://juanfont.github.io/headscale/development/ref/oidc/rel=canonical><linkhref=../configuration/rel=prev><linkhref=../exit-node/rel=next><linkrel=iconhref=../../assets/favicon.png><metaname=generatorcontent="mkdocs-1.6.1, mkdocs-material-9.5.49"><title>OIDC authentication - Headscale</title><linkrel=stylesheethref=../../assets/stylesheets/main.6f8fc17f.min.css><linkrel=stylesheethref=../../assets/stylesheets/palette.06af60db.min.css><linkrel=preconnecthref=https://fonts.gstatic.comcrossorigin><linkrel=stylesheethref="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback"><style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style><script>__md_scope=newURL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script><metaproperty=og:typecontent=website><metaproperty=og:titlecontent="OIDC authentication - Headscale"><metaproperty=og:descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaproperty=og:imagecontent=https://juanfont.github.io/headscale/development/assets/images/social/ref/oidc.png><metaproperty=og:image:typecontent=image/png><metaproperty=og:image:widthcontent=1200><metaproperty=og:image:heightcontent=630><metacontent=https://juanfont.github.io/headscale/development/ref/oidc/property=og:url><metaname=twitter:cardcontent=summary_large_image><metaname=twitter:titlecontent="OIDC authentication - Headscale"><metaname=twitter:descriptioncontent="An open source, self-hosted implementation of the Tailscale control server."><metaname=twitter:imagecontent=https://juanfont.github.io/headscale/development/assets/images/social/ref/oidc.png></head><bodydir=ltrdata-md-color-scheme=defaultdata-md-color-primary=whitedata-md-color-accent=indigo><inputclass=md-toggledata-md-toggle=drawertype=checkboxid=__drawerautocomplete=off><inputclass=md-toggledata-md-toggle=searchtype=checkboxid=__searchautocomplete=off><labelclass=md-overlayfor=__drawer></label><divdata-md-component=skip><ahref=#configuring-headscale-to-use-oidc-authenticationclass=md-skip> Skip to content </a></div><divdata-md-component=announce></div><divdata-md-color-scheme=defaultdata-md-component=outdatedhidden></div><headerclass=md-headerdata-md-component=header><navclass="md-header__inner md-grid"aria-label=Header><ahref=../..title=Headscaleclass="md-header__button md-logo"aria-label=Headscaledata-md-component=logo><imgsrc=../../logo/headscale3-dots.svgalt=logo></a><labelclass="md-header__button md-icon"for=__drawer><svgxmlns=http://www.w3.org/2000/svgviewbox="0 0 24 24"><pathd="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg></label><divclass=md-header__titledata-md-component=header-title><divclass=md-header__ellipsis><divclass=md-header__topic><spanclass=md-ellipsis> Headscale </span></div><divclass=md-header__topicdata-md-component=header-topic><spanclass=md-ellipsis> OIDC authentication </span></div></div></div><formclass=md-header__optiondata-md-component=palette><inputclass=md-optiondata-md-color-mediadata-md-color-scheme=defaultdata-md-color-primary=whitedata-md-color-accent=indigoaria-label="Switch to dark mode"type=radioname=__paletteid=__palette_0><labelclass="md-header__button md-icon"title="Switch to dark mode"for=__palette_1hidden><svgxmlns=http://www.w3.org/2000/svgviewbox="0 0 24 24"><pathd="M128a44000-444400044440004-444000-4-4m010a66001-6-6660
</span><spanid=__span-0-2><aid=__codelineno-0-2name=__codelineno-0-2href=#__codelineno-0-2></a><spanclass=w></span><spanclass=c1># Block further startup until the OIDC provider is healthy and available</span>
</span><spanid=__span-0-4><aid=__codelineno-0-4name=__codelineno-0-4href=#__codelineno-0-4></a><spanclass=w></span><spanclass=c1># Specified by your OIDC provider</span>
</span><spanid=__span-0-6><aid=__codelineno-0-6name=__codelineno-0-6href=#__codelineno-0-6></a><spanclass=w></span><spanclass=c1># Specified/generated by your OIDC provider</span>
</span><spanid=__span-0-9><aid=__codelineno-0-9name=__codelineno-0-9href=#__codelineno-0-9></a><spanclass=w></span><spanclass=c1># alternatively, set `client_secret_path` to read the secret from the file.</span>
</span><spanid=__span-0-10><aid=__codelineno-0-10name=__codelineno-0-10href=#__codelineno-0-10></a><spanclass=w></span><spanclass=c1># It resolves environment variables, making integration to systemd's</span>
</span><spanid=__span-0-13><aid=__codelineno-0-13name=__codelineno-0-13href=#__codelineno-0-13></a><spanclass=w></span><spanclass=c1># as third option, it's also possible to load the oidc secret from environment variables</span>
</span><spanid=__span-0-14><aid=__codelineno-0-14name=__codelineno-0-14href=#__codelineno-0-14></a><spanclass=w></span><spanclass=c1># set HEADSCALE_OIDC_CLIENT_SECRET to the required value</span>
</span><spanid=__span-0-16><aid=__codelineno-0-16name=__codelineno-0-16href=#__codelineno-0-16></a><spanclass=w></span><spanclass=c1># Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query</span>
</span><spanid=__span-0-17><aid=__codelineno-0-17name=__codelineno-0-17href=#__codelineno-0-17></a><spanclass=w></span><spanclass=c1># parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email".</span>
</span><spanid=__span-0-19><aid=__codelineno-0-19name=__codelineno-0-19href=#__codelineno-0-19></a><spanclass=w></span><spanclass=c1># Optional: Passed on to the browser login request – used to tweak behaviour for the OIDC provider</span>
</span><spanid=__span-0-23><aid=__codelineno-0-23name=__codelineno-0-23href=#__codelineno-0-23></a><spanclass=w></span><spanclass=c1># Optional: List allowed principal domains and/or users. If an authenticated user's domain is not in this list,</span>
</span><spanid=__span-0-24><aid=__codelineno-0-24name=__codelineno-0-24href=#__codelineno-0-24></a><spanclass=w></span><spanclass=c1># the authentication request will be rejected.</span>
</span><spanid=__span-0-27><aid=__codelineno-0-27name=__codelineno-0-27href=#__codelineno-0-27></a><spanclass=w></span><spanclass=c1># Optional. Note that groups from Keycloak have a leading '/'.</span>
</span><spanid=__span-0-34><aid=__codelineno-0-34name=__codelineno-0-34href=#__codelineno-0-34></a><spanclass=w></span><spanclass=c1># If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.</span>
</span><spanid=__span-0-35><aid=__codelineno-0-35name=__codelineno-0-35href=#__codelineno-0-35></a><spanclass=w></span><spanclass=c1># This will transform `first-name.last-name@example.com` to the user `first-name.last-name`</span>
</span><spanid=__span-0-36><aid=__codelineno-0-36name=__codelineno-0-36href=#__codelineno-0-36></a><spanclass=w></span><spanclass=c1># If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following</span>
</span></code></pre></div><h2id=azure-ad-example>Azure AD example<aclass=headerlinkhref=#azure-ad-exampletitle="Permanent link">¶</a></h2><p>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:</p><divclass="language-hcl highlight"><pre><span></span><code><spanid=__span-1-1><aid=__codelineno-1-1name=__codelineno-1-1href=#__codelineno-1-1></a><spanclass=kr>resource</span><spanclass=w></span><spanclass=nc>"azuread_application"</span><spanclass=w></span><spanclass=nv>"headscale"</span><spanclass=w></span><spanclass=p>{</span>
</span><spanid=__span-1-28><aid=__codelineno-1-28name=__codelineno-1-28href=#__codelineno-1-28></a><spanclass=c1> # Points at your running headscale instance</span>
</span></code></pre></div><p>And in your headscale <code>config.yaml</code>:</p><divclass="language-yaml highlight"><pre><span></span><code><spanid=__span-2-1><aid=__codelineno-2-1name=__codelineno-2-1href=#__codelineno-2-1></a><spanclass=nt>oidc</span><spanclass=p>:</span>
</span><spanid=__span-2-9><aid=__codelineno-2-9name=__codelineno-2-9href=#__codelineno-2-9></a><spanclass=w></span><spanclass=c1># Use your own domain, associated with Azure AD</span>
</span><spanid=__span-2-11><aid=__codelineno-2-11name=__codelineno-2-11href=#__codelineno-2-11></a><spanclass=w></span><spanclass=c1># Optional: Force the Azure AD account picker</span>
</span></code></pre></div><h2id=google-oauth-example>Google OAuth Example<aclass=headerlinkhref=#google-oauth-exampletitle="Permanent link">¶</a></h2><p>In order to integrate headscale with Google, you'll need to have a <ahref=https://console.cloud.google.com>Google Cloud Console</a> account.</p><p>Google OAuth has a <ahref="https://support.google.com/cloud/answer/9110914?hl=en">verification process</a> 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 <code>@example.com</code>), you don't need to go through the verification process.</p><p>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.</p><h3id=steps>Steps<aclass=headerlinkhref=#stepstitle="Permanent link">¶</a></h3><ol><li>Go to <ahref=https://console.cloud.google.com>Google Console</a> and login or create an account if you don't have one.</li><li>Create a project (if you don't already have one).</li><li>On the left hand menu, go to <code>APIs and services</code> -><code>Credentials</code></li><li>Click <code>Create Credentials</code> -><code>OAuth client ID</code></li><li>Under <code>Application Type</code>, choose <code>Web Application</code></li><li>For <code>Name</code>, enter whatever you like</li><li>Under <code>Authorised redirect URIs</code>, use <code>https://example.com/oidc/callback</code>, replacing example.com with your headscale URL.</li><li>Click <code>Save</code> at the bottom of the form</li><li>Take note of the <code>Client ID</code> and <code>Client secret</code>, you can also download it for reference if you need it.</li><li>Edit your headscale config, under <code>oidc</code>, filling in your <code>client_id</code> and <code>client_secret</code>: <divclass="language-yaml highlight"><pre><span></span><code><spanid=__span-3-1><aid=__codelineno-3-1name=__codelineno-3-1href=#__codelineno-3-1></a><spanclass=nt>oidc</span><spanclass=p>:</span>