diff --git a/coderd/userauth.go b/coderd/userauth.go index 4dd67844cfb0b..7a18b9790df04 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "net/mail" "strconv" "strings" @@ -219,12 +220,25 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { }) return } + usernameRaw, ok := claims["preferred_username"] + var username string + if ok { + username, _ = usernameRaw.(string) + } emailRaw, ok := claims["email"] if !ok { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "No email found in OIDC payload!", - }) - return + // Email is an optional claim in OIDC and + // instead the email is frequently sent in + // "preferred_username". See: + // https://github.com/coder/coder/issues/4472 + _, err = mail.ParseAddress(username) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "No email found in OIDC payload!", + }) + return + } + emailRaw = username } email, ok := emailRaw.(string) if !ok { @@ -243,11 +257,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { return } } - usernameRaw, ok := claims["preferred_username"] - var username string - if ok { - username, _ = usernameRaw.(string) - } // The username is a required property in Coder. We make a best-effort // attempt at using what the claims provide, but if that fails we will // generate a random username. diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 42ac974d4e5ee..9643351032a88 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -425,6 +425,15 @@ func TestUserOIDC(t *testing.T) { Username: "kyle", AllowSignups: true, StatusCode: http.StatusTemporaryRedirect, + }, { + // See: https://github.com/coder/coder/issues/4472 + Name: "UsernameIsEmail", + Claims: jwt.MapClaims{ + "preferred_username": "kyle@kwc.io", + }, + Username: "kyle", + AllowSignups: true, + StatusCode: http.StatusTemporaryRedirect, }, { Name: "WithPicture", Claims: jwt.MapClaims{