encore-auth — Claude Code skill: what it does, how to install
Protects Encore.ts endpoints with authentication, configures Gateway handlers, and propagates user context across internal services.
npx skills add encoredev/skills --skill encore-auth -a claude-code
npx skills add encoredev/skills --skill auth -a claude-codeWhat encore-auth does
The skill equips coding agents with knowledge of the Encore.ts authentication system. It covers creating a typed authHandler<AuthParams, AuthData>, registering it inside a Gateway, and marking endpoints with auth: true. Handlers read caller information via getAuthData() from ~encore/auth.
Included recipes show how to verify JWT tokens using jose, validate API keys against a database table, and parse session cookies. Unauthenticated requests to protected endpoints return 401 errors. For inter-service communication, credentials forward automatically across calls made with ~encore/clients.
An authHandler must be registered with a Gateway to take effect. For testing in Vitest, mock getAuthData via vi.spyOn, or pass explicit authData overrides to internal service clients.
What it helps with
- protect Encore.ts API endpoints using
auth: true - validate incoming JWTs, API keys, or session cookies
- access caller identity via
getAuthDatain route handlers - propagate authentication context across microservice calls
- mock user session data in Vitest unit tests
Example prompts for the agent
- Protect this endpoint so only authenticated users can access it
- Set up an authHandler that validates Bearer JWT tokens with jose
- Write a Vitest test mocking getAuthData for an auth-protected route
How to install encore-auth
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill encore-auth -a claude-codenpx skills add encoredev/skills --skill auth -a claude-code2Manually: put the files in place
~/.claude/skills/auth/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/auth/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill auth -a codex2Manually: put the files in place
.agents/skills/auth/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill auth -a cursor2Manually: put the files in place
.cursor/skills/auth/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install encoredev/skills auth2Manually: put the files in place
.github/skills/auth/SKILL.md1Skills CLI (Vercel)
npx skills add encoredev/skills --skill auth -a gemini-cli2Platform CLI
gemini skills install https://github.com/encoredev/skills --path encore/authManually: put the files in place
.devin/skills/auth/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add encoredev/skills --skill 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-auth
How to install encore-auth in Claude Code?
Run npx skills add encoredev/skills --skill encore-auth -a claude-code in your terminal.
What does the encore-auth skill do?
It guides coding agents on implementing Encore.ts authentication, including defining authHandler, attaching it to a Gateway, and reading caller identities with getAuthData.
How does authentication propagate between Encore services?
Authentication data forwards automatically during internal calls through ~encore/clients. You can also override it directly by providing custom authData in the call options.
Source text
SKILL.md · Apache-2.0
---
name: encore-auth
description: >-
Protect Encore.ts endpoints with authentication and authorize callers. Covers `authHandler`, `Gateway`, `getAuthData`, and `auth: true`.
when_to_use: >-
User wants to require login on an 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 (`getAuthData`), set up an `authHandler<AuthParams, AuthData>` or `Gateway`, return 401/403 from a handler, or set `auth: true` on `api(...)`. Trigger phrases: "protect this endpoint", "only authenticated users", "require login", "Authorization header", "bearer token", "401", "403", "who is calling", "current user".
---
# Encore Authentication
## Instructions
Encore.ts provides a built-in authentication system for identifying API callers and protecting endpoints.
### 1. Create an Auth Handler
```typescript
// auth.ts
import { Header, Gateway } from "encore.dev/api";
import { authHandler } from "encore.dev/auth";
// Define what the auth handler receives
interface AuthParams {
authorization: Header<"Authorization">;
}
// Define what authenticated requests will have access to
interface AuthData {
userID: string;
email: string;
role: "admin" | "user";
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
// Validate the token (example with JWT)
const token = params.authorization.replace("Bearer ", "");
const payload = await verifyToken(token);
if (!payload) {
throw APIError.unauthenticated("invalid token");
}
return {
userID: payload.sub,
email: payload.email,
role: payload.role,
};
}
);
// Register the auth handler with a Gateway
export const gateway = new Gateway({
authHandler: auth,
});
```
### 2. Protect Endpoints
```typescript
import { api } from "encore.dev/api";
// Protected endpoint - requires authentication
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
// Only authenticated users reach here
}
);
// Public endpoint - no authentication required
export const healthCheck = api(
{ method: "GET", path: "/health", expose: true },
async () => ({ status: "ok" })
);
```
### 3. Access Auth Data in Endpoints
```typescript
import { api } from "encore.dev/api";
import { getAuthData } from "~encore/auth";
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
const auth = getAuthData()!; // Non-null when auth: true
return {
userID: auth.userID,
email: auth.email,
role: auth.role,
};
}
);
```
## Auth Handler Behavior
| Scenario | Handler Returns | Result |
|----------|----------------|--------|
| Valid credentials | `AuthData` object | Request authenticated |
| Invalid credentials | Throws `APIError.unauthenticated()` | Treated as no auth |
| Other error | Throws other error | Request aborted |
## Auth with Endpoints
| Endpoint Config | Request Has Auth | Result |
|-----------------|------------------|--------|
| `auth: true` | Yes | Proceeds with auth data |
| `auth: true` | No | 401 Unauthenticated |
| `auth: false` or omitted | Yes | Proceeds (auth data available) |
| `auth: false` or omitted | No | Proceeds (no auth data) |
## Service-to-Service Auth Propagation
Auth data automatically propagates to internal service calls:
```typescript
import { user } from "~encore/clients";
import { getAuthData } from "~encore/auth";
export const getOrderWithUser = api(
{ method: "GET", path: "/orders/:id", expose: true, auth: true },
async ({ id }): Promise<OrderWithUser> => {
const auth = getAuthData()!;
// Auth is automatically propagated to this call
const orderUser = await user.getProfile();
return { order: await getOrder(id), user: orderUser };
}
);
```
### Overriding Auth Data
You can explicitly override auth data when making service-to-service calls:
```typescript
import { user } from "~encore/clients";
// Override auth data for this specific call
const adminUser = await user.getProfile(
{},
{ authData: { userID: "admin-123", email: "[email protected]", role: "admin" } }
);
```
## Common Auth Patterns
### JWT Token Validation
```typescript
import { jwtVerify } from "jose";
import { secret } from "encore.dev/config";
const jwtSecret = secret("JWTSecret");
async function verifyToken(token: string): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(jwtSecret())
);
return payload;
} catch {
return null;
}
}
```
### API Key Authentication
```typescript
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const apiKey = params.authorization;
const user = await db.queryRow<User>`
SELECT id, email, role FROM users WHERE api_key = ${apiKey}
`;
if (!user) {
throw APIError.unauthenticated("invalid API key");
}
return {
userID: user.id,
email: user.email,
role: user.role,
};
}
);
```
### Cookie-Based Auth
```typescript
interface AuthParams {
cookie: Header<"Cookie">;
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const sessionId = parseCookie(params.cookie, "session");
if (!sessionId) {
throw APIError.unauthenticated("no session");
}
const session = await getSession(sessionId);
if (!session || session.expiresAt < new Date()) {
throw APIError.unauthenticated("session expired");
}
return {
userID: session.userID,
email: session.email,
role: session.role,
};
}
);
```
## Testing with Auth
Mock authentication in tests using Vitest:
```typescript
import { describe, it, expect, vi } from "vitest";
import * as auth from "~encore/auth";
import { getProfile } from "./api";
describe("authenticated endpoints", () => {
it("returns profile for authenticated user", async () => {
// Mock getAuthData to return test user
const spy = vi.spyOn(auth, "getAuthData");
spy.mockImplementation(() => ({
userID: "test-user-123",
email: "[email protected]",
role: "user",
}));
const profile = await getProfile();
expect(profile.email).toBe("[email protected]");
spy.mockRestore();
});
});
```
The mock does not reach calls made through `~encore/clients`. To call an `auth: true` endpoint on another service from a test, pass `{ authData: ... }` as the second argument (see "Overriding Auth Data" above).
## Guidelines
- Auth handlers must be registered with a Gateway
- Use `getAuthData()` from `~encore/auth` to access auth data
- `getAuthData()` returns `null` in unauthenticated requests
- Auth data propagates automatically in service-to-service calls
- Throw `APIError.unauthenticated()` for invalid credentials
- Keep auth handlers fast - they run on every authenticated request
© encoredev · Apache-2.0 · encoredev/skills