usememos/memos · error · ErrUnauthenticated

CodeUnauthenticated

CodeUnauthenticated

Error message

authentication required

What it means

ErrUnauthenticated is the sentinel error returned by the APIV1Service Authorizer when a request carries no valid credentials (missing, malformed, or expired Authorization header) and the target RPC does not allow anonymous access. The Connect interceptor maps it to CodeUnauthenticated and the gRPC-Gateway middleware maps it to HTTP 401, so both transports behave identically. It is thrown before any service logic runs.

Source

Thrown at server/router/api/v1/authz.go:15

package v1

import (
	"context"
	"errors"

	"github.com/usememos/memos/internal/profile"
	"github.com/usememos/memos/server/auth"
	"github.com/usememos/memos/store"
)

// ErrUnauthenticated is returned by the Authorizer when a request must be rejected
// for lack of valid credentials. Each transport maps it to its own status code
// (Connect: CodeUnauthenticated, gRPC-Gateway: HTTP 401).
var ErrUnauthenticated = errors.New("authentication required")

// Authorizer is the single source of truth for method-level access control.
//
// It authenticates a request from its Authorization header and decides whether the
// (possibly anonymous) caller may reach a given RPC procedure. The Connect
// interceptor and the gRPC-Gateway middleware share one Authorizer so both
// transports enforce identical rules.
//
// Role-based authorization (admin checks) stays in the service layer; this type
// governs only authentication and anonymous access.
type Authorizer struct {
	authenticator *auth.Authenticator
	profile       *profile.Profile
}

// NewAuthorizer creates an Authorizer backed by the given store, token secret, and
// instance profile.
func NewAuthorizer(store *store.Store, secret string, profile *profile.Profile) *Authorizer {

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Attach a valid access token: Authorization: Bearer <access_token> obtained from SignIn/SignUp.
  2. If the token expired, call the refresh endpoint to get a new access token before retrying.
  3. If the endpoint should be reachable without login (e.g. GetInstanceStatus for the sign-in page), verify it is registered as an anonymous-allowed method in server/router/api/v1/acl_config.go.
  4. Check that the token value was not truncated or prefixed with extra characters when copied.

Example fix

// before
curl http://localhost:5230/memos.api.v1.MemoService/ListMemos \
  -H 'Content-Type: application/json' -d '{}'

// after
curl http://localhost:5230/memos.api.v1.MemoService/ListMemos \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" -d '{}'
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure a token exists before calling a protected RPC
if (!accessToken) {
  throw new Error('Sign in required before calling protected APIs');
}

Try / catch

try {
  const resp = await client.listMemos({});
} catch (e) {
  if (e.code === 'unauthenticated') {
    await signOut(); // clear stale token state, redirect to sign-in
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any non-public RPC (e.g. MemoService.ListMemos, UserService.GetUser) without an Authorization header; sending an expired access token; sending a token with an unsupported scheme (the authenticator only accepts its defined schemes); or hitting a route not listed as anonymously accessible in acl_config.go.

Common situations: Frontend forgot to attach the access token via the Connect client interceptor; token expired and the refresh flow did not run (refresh token also expired or revoked); CLI/script integration using raw HTTP without Bearer auth; testing a locally protected route with curl.

Understand the failure class

Related errors


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