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

  1. Use the refreshToken field (not accessToken) when calling the token refresh endpoint
  2. In client storage, keep distinct keys for access and refresh tokens and read them explicitly
  3. 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

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

Related errors


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