semaphoreui/semaphore · error
OIDC sign-in failed: the provider returned no user ID (sub…
Error message
OIDC sign-in failed: the provider returned no user ID (sub claim). Contact your administrator.
What it means
oidcRedirect requires a stable user identifier (sub claim) to resolve or create the local account. If claims.sub is empty after reading token/userinfo claims, it logs 'oidc provider %s returned no sub claim' and returns HTTP 502. OIDC mandates sub; its absence means a non-conformant provider or a claim mapping that dropped it.
Solutions
- Check the provider config's claim mapping and ensure the subject claim is mapped from 'sub'
- Decode the returned ID token (jwt.io or logs) to confirm whether sub is actually present
- If the IdP omits sub, switch to a conformant IdP config/realm or file an issue with the vendor
- Update provider firmware/config for non-conformant identity products
Example fix
// before
claims_mapping: {sub: "user_id"} // IdP never emits user_id
// after
claims_mapping: {sub: "sub"} Defensive patterns
Strategy: validation
Validate before calling
var payload map[string]any
json.Unmarshal(idTokenBytes, &payload)
if sub, _ := payload["sub"].(string); sub == "" {
return errors.New("provider token has no sub claim")
} Type guard
func hasSub(claims map[string]any) bool {
s, ok := claims["sub"].(string)
return ok && s != ""
} Prevention
- Verify provider conformance (sub is mandatory in OIDC)
- Keep claim mapping for sub pointed at 'sub'
- Test a full login against the IdP before go-live
When it happens
Trigger: IdP returns an ID token/userinfo without a sub claim, claimOidcToken/claimOidcUserInfo mapping is configured to a wrong claim name so sub never gets populated, or a token from a non-conformant OAuth2 (not OIDC) endpoint.
Common situations: Custom/legacy IdPs that omit sub; misconfigured claim mapping in Semaphore's provider config pointing sub at a custom attribute; providers that only return email without subject.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- OIDC sign-in failed: could not read user info from the…
- Account linking must be initiated with a POST request.
- You must be signed in to link an external account.
- OIDC sign-in failed: state cookie is missing. Try signing…
- OIDC sign-in failed: invalid state. Try signing in again.
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/4588692858f8cdbc.
Report an issue: GitHub.
Appendix: source
Thrown at api/login.go:951
claims.emailVerified = oidcEmailVerified(userInfo, provider)
}
}
claims.username = getRandomUsername()
if userInfo.Profile == "" {
claims.name = getRandomProfileName()
}
}
if err != nil {
log.Error(err.Error())
http.Error(w, "OIDC sign-in failed: could not read user info from the provider. Contact your administrator.", http.StatusBadGateway)
return
}
if claims.sub == "" {
log.Error(fmt.Errorf("oidc provider %s returned no sub claim", pid))
http.Error(w, "OIDC sign-in failed: the provider returned no user ID (sub claim). Contact your administrator.", http.StatusBadGateway)
return
}
if stateData.Link {
session, ok := getSession(r)
if !ok || !session.IsVerified() {
http.Error(w, "You must be signed in to link an external account.", http.StatusUnauthorized)
return
}
sessionUser, uErr := helpers.Store(r).GetUser(session.UserID)
if uErr != nil {
log.Error(uErr.Error())
http.Error(w, "Failed to link external account.", http.StatusInternalServerError)
return
}
if lErr := linkExternalIdentity(helpers.Store(r), sessionUser, db.IdentityTypeOidc, pid, claims.sub); lErr != nil {View on GitHub (pinned to 1774ccb71a)