| name | encore-go-auth |
| description | Protect Encore Go endpoints with authentication and authorize callers. Covers `auth.AuthHandler`, `auth.UserID`, the `Authorization` header, and `//encore:api auth`. |
| when_to_use | User wants to require login on a Go endpoint, restrict an endpoint to authenticated/signed-in users, validate a bearer token / JWT / API key from an `Authorization` header, read the current user inside a handler (`auth.UserID()` / `auth.Data()`), define an `auth.AuthHandler`, return `errs.Unauthenticated` from a handler, or use `//encore:api auth` on a handler. Trigger phrases: "protect this endpoint", "only authenticated users", "require login", "Authorization header", "bearer token", "401", "403", "who is calling", "current user". |
Encore Go Authentication
Instructions
Encore Go provides a built-in authentication system using the //encore:authhandler annotation.
1. Create an Auth Handler
package auth
import (
"context"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
)
type AuthParams struct {
Authorization string `header:"Authorization"`
}
type AuthData struct {
UserID string
Email string
Role string
}
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
token := strings.TrimPrefix(params.Authorization, "Bearer ")
payload, err := verifyToken(token)
if err != nil {
return "", nil, &errs.Error{
Code: errs.Unauthenticated,
Message: "invalid token",
}
}
return auth.UID(payload.UserID), &AuthData{
UserID: payload.UserID,
Email: payload.Email,
Role: payload.Role,
}, nil
}
2. Protect Endpoints
package user
import "context"
func GetProfile(ctx context.Context) (*Profile, error) {
}
func Health(ctx context.Context) (*HealthResponse, error) {
return &HealthResponse{Status: "ok"}, nil
}
3. Access Auth Data in Endpoints
package user
import (
"context"
"encore.dev/beta/auth"
myauth "myapp/auth"
)
func GetProfile(ctx context.Context) (*Profile, error) {
userID, ok := auth.UserID()
if !ok {
}
data := auth.Data().(*myauth.AuthData)
return &Profile{
UserID: string(userID),
Email: data.Email,
Role: data.Role,
}, nil
}
Auth Handler Signature
The auth handler must:
- Have the
//encore:authhandler annotation
- Accept
context.Context and a params struct pointer
- Return
(auth.UID, *YourAuthData, error)
func MyAuthHandler(ctx context.Context, params *Params) (auth.UID, *AuthData, error)
Auth Handler Behavior
| Scenario | Returns | Result |
|---|
| Valid credentials | (uid, data, nil) | Request authenticated |
| Invalid credentials | ("", nil, err) with errs.Unauthenticated | 401 response |
| Other error | ("", nil, err) | Request aborted |
Common Auth Patterns
JWT Token Validation
import "github.com/golang-jwt/jwt/v5"
var secrets struct {
JWTSecret string
}
func verifyToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(t *jwt.Token) (interface{}, error) {
return []byte(secrets.JWTSecret), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}
API Key Authentication
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
apiKey := params.Authorization
user, err := db.QueryRow[User](ctx, `
SELECT id, email, role FROM users WHERE api_key = $1
`, apiKey)
if err != nil {
return "", nil, &errs.Error{
Code: errs.Unauthenticated,
Message: "invalid API key",
}
}
return auth.UID(user.ID), &AuthData{
UserID: user.ID,
Email: user.Email,
Role: user.Role,
}, nil
}
Cookie-Based Auth
type AuthParams struct {
Cookie string `header:"Cookie"`
}
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
sessionID := parseCookie(params.Cookie, "session")
if sessionID == "" {
return "", nil, &errs.Error{
Code: errs.Unauthenticated,
Message: "no session",
}
}
session, err := getSession(ctx, sessionID)
if err != nil || session.ExpiresAt.Before(time.Now()) {
return "", nil, &errs.Error{
Code: errs.Unauthenticated,
Message: "session expired",
}
}
return auth.UID(session.UserID), &AuthData{
UserID: session.UserID,
Email: session.Email,
Role: session.Role,
}, nil
}
Multi-Source Auth (Cookie + Header + Query)
Auth params can extract data from multiple sources:
import "net/http"
type AuthParams struct {
SessionCookie *http.Cookie `cookie:"session"`
Authorization string `header:"Authorization"`
ClientID string `query:"client_id"`
}
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
if params.SessionCookie != nil {
return authenticateWithSession(ctx, params.SessionCookie.Value)
}
if params.Authorization != "" {
return authenticateWithToken(ctx, params.Authorization)
}
return "", nil, &errs.Error{
Code: errs.Unauthenticated,
Message: "no credentials provided",
}
}
Service-to-Service Auth
Auth data automatically propagates in internal service calls:
package order
import (
"context"
"myapp/user"
)
func GetOrderWithUser(ctx context.Context, params *GetOrderParams) (*OrderWithUser, error) {
order, err := getOrder(ctx, params.ID)
if err != nil {
return nil, err
}
profile, err := user.GetProfile(ctx)
if err != nil {
return nil, err
}
return &OrderWithUser{Order: order, User: profile}, nil
}
Testing with Auth
Override auth data in tests using auth.WithContext:
package user_test
import (
"context"
"testing"
"encore.dev/beta/auth"
myauth "myapp/auth"
"myapp/user"
)
func TestGetProfile(t *testing.T) {
ctx := auth.WithContext(
context.Background(),
auth.UID("test-user-123"),
&myauth.AuthData{
UserID: "test-user-123",
Email: "test@example.com",
Role: "user",
},
)
profile, err := user.GetProfile(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if profile.Email != "test@example.com" {
t.Errorf("expected test@example.com, got %s", profile.Email)
}
}
Guidelines
- Only one
//encore:authhandler per application
- Return
auth.UID as the first return value (user identifier)
- Return your custom
AuthData struct as second value
- Use
auth.UserID() to get the authenticated user ID
- Use
auth.Data() and type assert to get full auth data
- Auth propagates automatically in service-to-service calls
- Use
auth.WithContext() to override auth in tests
- Keep auth handlers fast - they run on every authenticated request