usememos/memos · error

missing access token from authorization response

Error message

missing access token from authorization response

What it means

After a successful OAuth2 authorization-code exchange (no error from conf.Exchange), the returned token's AccessToken field is empty. The provider accepted the exchange but issued a response without an access_token field, which the Memos IdP layer treats as a protocol violation rather than proceeding with an empty credential.

Source

Thrown at internal/idp/oauth2/oauth2.go:76

	}

	// Prepare token exchange options
	opts := []oauth2.AuthCodeOption{}

	// Add PKCE code_verifier if provided
	if codeVerifier != "" {
		opts = append(opts, oauth2.SetAuthURLParam("code_verifier", codeVerifier))
	}

	token, err := conf.Exchange(ctx, code, opts...)
	if err != nil {
		return "", errors.Wrap(err, "failed to exchange access token")
	}

	// Use the standard AccessToken field instead of Extra()
	// This is more reliable across different OAuth providers
	if token.AccessToken == "" {
		return "", errors.New("missing access token from authorization response")
	}

	return token.AccessToken, nil
}

// UserInfo returns the parsed user information using the given OAuth2 token.
func (p *IdentityProvider) UserInfo(ctx context.Context, token string) (*idp.IdentityProviderUserInfo, error) {
	client := &http.Client{Timeout: userInfoRequestTimeout}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.config.UserInfoUrl, nil)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create http request")
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	resp, err := client.Do(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get user information")
	}

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Verify the IdP config in Memos: authorization URL, token URL, and client credentials
  2. Test the token endpoint directly with curl (POST code + redirect_uri + client credentials) and confirm the JSON contains a non-empty access_token
  3. Check the provider logs for the actual token response; fix the provider or its URL configuration
  4. If scopes/response types were customized, return to the provider's documented defaults
Defensive patterns

Strategy: try-catch

Validate before calling

// Before redirecting users, smoke-test the provider's token endpoint contract
// (run once at IdP config time, not per login):
// POST <tokenURL> with client credentials + a dummy code.
// Expect 4xx from the provider, NOT a 200 without access_token — a 200 with
// no access_token field means a misconfigured token URL or non-standard provider.
func tokenEndpointLooksStandard(tokenURL, clientID, clientSecret string) bool {
  form := url.Values{"grant_type": {"authorization_code"}, "code": {"x"}}
  req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(form.Encode()))
  req.SetBasicAuth(clientID, clientSecret)
  req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  resp, err := http.DefaultClient.Do(req)
  if err != nil { return false }
  defer resp.Body.Close()
  if resp.StatusCode == 200 {
    var body map[string]any
    json.NewDecoder(resp.Body).Decode(&body)
    _, hasToken := body["access_token"]
    return hasToken
  }
  return true // proper providers reject the dummy code
}

Try / catch

// On IdP login failure, surface a config-oriented message and keep the session unauthenticated
if _, err := idpSVC.Exchange(ctx, code); err != nil {
  if strings.Contains(err.Error(), "missing access token") {
    return echo.NewHTTPError(http.StatusBadGateway, "identity provider returned an empty token; check its token endpoint configuration")
  }
  return err
}

Prevention

When it happens

Trigger: Completing OAuth2 login where the token endpoint response omits access_token: misconfigured provider, a proxy mangling the response, custom IdP implementations returning non-standard JSON, or unusual token endpoint response_type configurations.

Common situations: Self-hosted custom OAuth providers (e.g. internal SSO) with non-RFC6749 token responses; wrong token URL configured in the IdP settings so a 200 HTML page is parsed as an empty token; provider version changes altering the token endpoint contract.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/656b14ad4c11dcb7. Report an issue: GitHub.