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
- Verify the IdP config in Memos: authorization URL, token URL, and client credentials
- Test the token endpoint directly with curl (POST code + redirect_uri + client credentials) and confirm the JSON contains a non-empty access_token
- Check the provider logs for the actual token response; fix the provider or its URL configuration
- 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
- Smoke-test custom OAuth providers against the token endpoint before rollout
- Keep IdP authorization/token URLs exactly as documented by the provider
- Monitor token-exchange failures per provider to catch contract changes early
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
- Unauthenticated
- Unauthenticated
- config.oauth2Config is required
- config.oauth2Config.scopes is required
- config.oauth2Config.fieldMapping.identifier is required
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/656b14ad4c11dcb7.
Report an issue: GitHub.