OIDC: Fetch UserInfo to get EmailVerified if necessary (#2493)

This commit is contained in:
Benjamin Staffin 2025-03-27 05:39:29 -04:00 committed by GitHub
parent badbb68217
commit b5953d689c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 32 additions and 5 deletions

View File

@ -83,6 +83,10 @@ The new policy can be used by setting the environment variable
- It is now possible to inspect running goroutines and take profiles
- View of config, policy, filter, ssh policy per node, connected nodes and
DERPmap
- OIDC: Fetch UserInfo to get EmailVerified if necessary
[#2493](https://github.com/juanfont/headscale/pull/2493)
- If a OIDC provider doesn't include the `email_verified` claim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
## 0.25.1 (2025-02-25)

View File

@ -234,7 +234,14 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
return
}
idToken, err := a.extractIDToken(req.Context(), code, state)
oauth2Token, err := a.getOauth2Token(req.Context(), code, state)
if err != nil {
httpError(writer, err)
return
}
idToken, err := a.extractIDToken(req.Context(), oauth2Token)
if err != nil {
httpError(writer, err)
return
@ -273,6 +280,16 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
return
}
// If EmailVerified is missing, we can try to get it from UserInfo
if !claims.EmailVerified {
var userinfo *oidc.UserInfo
userinfo, err = a.oidcProvider.UserInfo(req.Context(), oauth2.StaticTokenSource(oauth2Token))
if err != nil {
util.LogErr(err, "could not get userinfo; email cannot be verified")
}
claims.EmailVerified = types.FlexibleBoolean(userinfo.EmailVerified)
}
user, err := a.createOrUpdateUserFromClaim(&claims)
if err != nil {
httpError(writer, err)
@ -333,13 +350,12 @@ func extractCodeAndStateParamFromRequest(
return code, state, nil
}
// extractIDToken takes the code parameter from the callback
// and extracts the ID token from the oauth2 token.
func (a *AuthProviderOIDC) extractIDToken(
// getOauth2Token exchanges the code from the callback for an oauth2 token.
func (a *AuthProviderOIDC) getOauth2Token(
ctx context.Context,
code string,
state string,
) (*oidc.IDToken, error) {
) (*oauth2.Token, error) {
var exchangeOpts []oauth2.AuthCodeOption
if a.cfg.PKCE.Enabled {
@ -356,7 +372,14 @@ func (a *AuthProviderOIDC) extractIDToken(
if err != nil {
return nil, NewHTTPError(http.StatusForbidden, "invalid code", fmt.Errorf("could not exchange code for token: %w", err))
}
return oauth2Token, err
}
// extractIDToken extracts the ID token from the oauth2 token.
func (a *AuthProviderOIDC) extractIDToken(
ctx context.Context,
oauth2Token *oauth2.Token,
) (*oidc.IDToken, error) {
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
return nil, NewHTTPError(http.StatusBadRequest, "no id_token", errNoOIDCIDToken)