usememos/memos · error
Unauthenticated
Unauthenticated
Error message
invalid token type: expected refresh token
What it means
ParseRefreshToken accepted a JWT with valid signature, issuer, and audience, but its custom Type claim is not "refresh" — i.e. an access token (or other token type) was supplied where a refresh token is required. Unauthenticated.
Source
Thrown at server/auth/token.go:246
}
if claims.Type != "access" {
return nil, errors.New("invalid token type: expected access token")
}
return claims, nil
}
// ParseRefreshToken parses and validates a refresh token.
func ParseRefreshToken(tokenString string, secret []byte) (*RefreshTokenClaims, error) {
claims := &RefreshTokenClaims{}
_, err := jwt.ParseWithClaims(tokenString, claims, verifyJWTKeyFunc(secret),
jwt.WithIssuer(Issuer),
jwt.WithAudience(RefreshTokenAudienceName),
)
if err != nil {
return nil, err
}
if claims.Type != "refresh" {
return nil, errors.New("invalid token type: expected refresh token")
}
return claims, nil
}
View on GitHub (pinned to 14d757ce1f)
Solutions
- Use the refreshToken field (not accessToken) when calling the token refresh endpoint
- In client storage, keep distinct keys for access and refresh tokens and read them explicitly
- Log which token type was sent when debugging; decode the JWT payload and check its `typ`/type claim equals refresh
Example fix
// before refresh(cached.accessToken) // after refresh(cached.refreshToken)
Defensive patterns
Strategy: type-guard
Validate before calling
// Decode (without verifying) and check the type claim before using the token
func isRefreshToken(jwtStr string) bool {
parts := strings.Split(jwtStr, ".")
if len(parts) != 3 { return false }
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil { return false }
var claims struct{ Type string `json:"type"` }
if json.Unmarshal(payload, &claims) != nil { return false }
return claims.Type == "refresh"
} Type guard
func isRefreshTokenString(s string) bool {
p := strings.Split(s, ".")
if len(p) != 3 { return false }
raw, err := base64.RawURLEncoding.DecodeString(p[1]); if err != nil { return false }
var c struct{ Type string `json:"type"` }
return json.Unmarshal(raw, &c) == nil && c.Type == "refresh"
} Try / catch
// Fail fast with an explicit message when the wrong token is supplied
if _, err := auth.ParseRefreshToken(tok, secret); err != nil {
if strings.Contains(err.Error(), "expected refresh token") {
return errors.New("supplied an access token where a refresh token is required")
}
return err
} Prevention
- Store access and refresh tokens under distinct, clearly named keys
- Name variables for their role (accessToken vs refreshToken), never generic 'token'
- Add a debug claim dump when integrating new clients
When it happens
Trigger: Sending an access token to the refresh endpoint, persisting/submitting the wrong token field from client storage, or a custom integration mixing up the two token strings returned by the login response.
Common situations: Client bugs that store both tokens under one key or swap them; API consumers assuming a single-token model; token payload changes after an instance upgrade while old clients cache the wrong field.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthenticated
- missing access token from authorization response
- deployment configuration disables password authentication fo
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/a3811ac0f19b306e.
Report an issue: GitHub.