oauth2-proxy/oauth2-proxy · error

unable to extract username from userinfo endpoint: %v

Error message

unable to extract username from userinfo endpoint: %v

What it means

Same EnrichSession flow in providers/srht.go: after successfully extracting email, the provider reads data.me.username; if that path is missing or not a string, it returns "unable to extract username from userinfo endpoint: %v". It populates session.PreferredUsername and session.User, so failure here leaves the session without a username.

Source

Thrown at providers/srht.go:98

		SetHeader("Content-Type", "application/json").
		SetHeader("Authorization", "Bearer "+s.AccessToken).
		WithBody(bytes.NewBufferString(`{"query": "{ me { username, email } }"}`)).
		Do().
		UnmarshalSimpleJSON()
	if err != nil {
		logger.Errorf("failed making request %v", err)
		return err
	}

	email, err := json.GetPath("data", "me", "email").String()
	if err != nil {
		return fmt.Errorf("unable to extract email from userinfo endpoint: %v", err)
	}
	s.Email = email

	username, err := json.GetPath("data", "me", "username").String()
	if err != nil {
		return fmt.Errorf("unable to extract username from userinfo endpoint: %v", err)
	}
	s.PreferredUsername = username
	s.User = username

	return nil
}

// ValidateSession validates the AccessToken
func (p *SourceHutProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
	return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken))
}

View on GitHub (pinned to 33c2eb92de)

Solutions

  1. Inspect the wrapped %v message and the raw response body; a GraphQL 'errors' array with 200 status is the usual culprit.
  2. Re-authenticate: an expired/revoked access token can yield error-shaped responses; ensure token refresh happens before EnrichSession.
  3. Verify requested scopes include the fields needed for username and that the configured srht API base URL matches your instance.
  4. Add a check for a GraphQL errors key in the response before path extraction and surface the upstream error message.

Example fix

// before
username, err := json.GetPath("data", "me", "username").String()
// after
if errs := json.Get("errors"); errs != nil {
    return fmt.Errorf("sourcehut API returned errors: %v", errs)
}
username, err := json.GetPath("data", "me", "username").String()
Defensive patterns

Strategy: try-catch

Validate before calling

var probe map[string]any
json.NewDecoder(resp.Body).Decode(&probe)
if e, ok := probe["errors"]; ok { /* GraphQL error payload despite HTTP 200 */ _ = e }
if m, ok := probe["data"].(map[string]any); ok {
    if me, ok := m["me"].(map[string]any); ok {
        if _, ok := me["username"].(string); !ok { /* username missing */ }
    }
}

Type guard

func hasUsername(body map[string]any) bool {
    d, ok := body["data"].(map[string]any)
    if !ok { return false }
    me, ok := d["me"].(map[string]any)
    if !ok { return false }
    u, ok := me["username"].(string)
    return ok && u != ""
}

Try / catch

if err := p.EnrichSession(ctx, s, tok); err != nil {
    if strings.Contains(err.Error(), "unable to extract username") {
        // token likely expired/revoked or scope missing; trigger re-auth and log body
        return fmt.Errorf("sourcehut username unavailable (re-auth suggested): %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: EnrichSession on the Sourcehut provider gets a 200 JSON response where data.me.username is absent, null, or non-string — e.g. truncated response, error object under 'errors' instead of 'data', or a proxy stripping fields.

Common situations: Token scope too narrow for account metadata; SourceHut GraphQL returning {"errors":[...]} due to an expired/revoked token while HTTP status stays 200; network middleboxes (corporate proxy) mangling the body; wrong instance URL (sr.ht vs custom SourceHut deployment) with different schema.

Related errors


AI-assisted analysis of oauth2-proxy/oauth2-proxy@33c2eb92de (2026-09-06). Data as JSON: /api/errors/359351f49f89cf09. Report an issue: GitHub.