encore-go-auth — Claude Code skill: setup
This skill helps secure Encore Go endpoints and identify callers. Use it for JWTs, API keys, bearer tokens, or session cookies.
npx skills add encoredev/skills --skill encore-go-auth -a claude-code
npx skills add encoredev/skills --skill go-auth -a claude-codeWhat encore-go-auth does
The skill explains Encore Go’s built-in authentication flow. An //encore:authhandler function receives request parameters and returns an auth.UID, auth data, and an error. It can read the Authorization header, validate a bearer token or API key, and return errs.Unauthenticated when credentials fail.
Endpoint access is declared with //encore:api auth; public routes use //encore:api public. Handler code can call auth.UserID() and auth.Data() to obtain the caller’s ID, email, and role. Authentication parameters may also come from cookies or query strings.
The handler signature is fixed: context.Context, a pointer to a parameter struct, and (auth.UID, *AuthData, error). Without //encore:authhandler, Encore will not treat the function as an authentication handler.
What it helps with
- protect an Encore Go endpoint with authentication
- validate a bearer token or JWT from
Authorization - read the current caller’s ID, email, and role
- return a 401 response for invalid credentials
- accept authentication from a cookie or API key
Example prompts for the agent
- Add
//encore:api authto the profile endpoint and read the caller withauth.UserID()andauth.Data(). - Create an
//encore:authhandlerthat validates a bearer token fromAuthorizationand returnserrs.Unauthenticatedon failure. - Show how to authenticate an Encore Go request with a session cookie.
How to install encore-go-auth
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill encore-go-auth -a claude-codenpx skills add encoredev/skills --skill go-auth -a claude-code2Manually: put the files in place
~/.claude/skills/go-auth/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/go-auth/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill go-auth -a codex2Manually: put the files in place
.agents/skills/go-auth/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill go-auth -a cursor2Manually: put the files in place
.cursor/skills/go-auth/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install encoredev/skills go-auth2Manually: put the files in place
.github/skills/go-auth/SKILL.md1Skills CLI (Vercel)
npx skills add encoredev/skills --skill go-auth -a gemini-cli2Platform CLI
gemini skills install https://github.com/encoredev/skills --path encore/go-authManually: put the files in place
.devin/skills/go-auth/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add encoredev/skills --skill go-auth -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about encore-go-auth
How do I install encore-go-auth in Claude Code?
Run npx skills add encoredev/skills --skill encore-go-auth -a claude-code, then run npx skills add encoredev/skills --skill go-auth -a claude-code. The skill file is available at ~/.claude/skills/go-auth/SKILL.md.
What does encore-go-auth do?
It guides Encore Go authentication setup: defining an auth.AuthHandler, securing endpoints with //encore:api auth, and reading the current user’s auth data.
What signature must an Encore auth handler use?
The function needs //encore:authhandler, accepts context.Context and a pointer to a parameter struct, and returns (auth.UID, *AuthData, error). Invalid credentials use errs.Unauthenticated.
Source text
SKILL.md · Apache-2.0
---
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
```go
package auth
import (
"context"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
)
// AuthParams defines what the auth handler receives
type AuthParams struct {
Authorization string `header:"Authorization"`
}
// AuthData defines what authenticated requests have access to
type AuthData struct {
UserID string
Email string
Role string
}
//encore:authhandler
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
```go
package user
import "context"
// Protected endpoint - requires authentication
//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
// Only authenticated users reach here
}
// Public endpoint - no authentication required
//encore:api public method=GET path=/health
func Health(ctx context.Context) (*HealthResponse, error) {
return &HealthResponse{Status: "ok"}, nil
}
```
### 3. Access Auth Data in Endpoints
```go
package user
import (
"context"
"encore.dev/beta/auth"
myauth "myapp/auth" // Import your auth package
)
//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
// Get the user ID
userID, ok := auth.UserID()
if !ok {
// Should not happen with auth endpoint
}
// Get full auth data
data := auth.Data().(*myauth.AuthData)
return &Profile{
UserID: string(userID),
Email: data.Email,
Role: data.Role,
}, nil
}
```
## Auth Handler Signature
The auth handler must:
1. Have the `//encore:authhandler` annotation
2. Accept `context.Context` and a params struct pointer
3. Return `(auth.UID, *YourAuthData, error)`
```go
//encore:authhandler
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
```go
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
```go
//encore:authhandler
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
```go
type AuthParams struct {
Cookie string `header:"Cookie"`
}
//encore:authhandler
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:
```go
import "net/http"
type AuthParams struct {
SessionCookie *http.Cookie `cookie:"session"` // From cookie
Authorization string `header:"Authorization"` // From header
ClientID string `query:"client_id"` // From query string
}
//encore:authhandler
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
// Try session cookie first
if params.SessionCookie != nil {
return authenticateWithSession(ctx, params.SessionCookie.Value)
}
// Fall back to Authorization header
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:
```go
package order
import (
"context"
"myapp/user" // Import the user service
)
//encore:api auth method=GET path=/orders/:id
func GetOrderWithUser(ctx context.Context, params *GetOrderParams) (*OrderWithUser, error) {
order, err := getOrder(ctx, params.ID)
if err != nil {
return nil, err
}
// Auth is automatically propagated to this call
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`:
```go
package user_test
import (
"context"
"testing"
"encore.dev/beta/auth"
myauth "myapp/auth"
"myapp/user"
)
func TestGetProfile(t *testing.T) {
// Create a context with auth data
ctx := auth.WithContext(
context.Background(),
auth.UID("test-user-123"),
&myauth.AuthData{
UserID: "test-user-123",
Email: "[email protected]",
Role: "user",
},
)
// Call the endpoint with the authenticated context
profile, err := user.GetProfile(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if profile.Email != "[email protected]" {
t.Errorf("expected [email protected], 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
© encoredev · Apache-2.0 · encoredev/skills