56 results available
Sign in
Skills directory

auth

encoredev

·

Last changed
Hardened

This skill has been hardened by Chainguard

Last changed

This version passed the hardening gate. Hardening applied 9 targeted changes and preserved all other text. Classified findings before hardening: 0. Classified findings closed: 0. Classified findings remaining: 0. Unclassified scanner findings remaining: 0. Review findings closed: 10. Review findings remaining: 1.


Open before hardening

None

Open after hardening

None


Expand
File
Group
Subtype
Severity
Status

No findings available

0 findings


SKILL.md

Encore Authentication

Instructions

Encore.ts provides a built-in authentication system for identifying API callers and protecting endpoints.

1. Create an Auth Handler

// 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,
});

In real code, parse the Authorization header strictly: accept it only when it starts with the Bearer scheme (compared case insensitively) followed by exactly one token, and throw an unauthenticated error for any other value. Before returning the auth data, confirm that the subject and email claims are present strings and that the role claim is exactly admin or user; for any other role, treat the caller as a plain user or look the role up on the server. Each example that throws an API error also needs that error class imported from the same module as Header and Gateway, and the token verification helper is the one shown in the JWT Token Validation section.

2. Protect Endpoints

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

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

ScenarioHandler ReturnsResult

Valid credentials

AuthData object

Request authenticated

Invalid credentials

Throws APIError.unauthenticated()

Treated as no auth

Other error

Throws other error

Request aborted

On endpoints where auth: true is not set, a request carrying an invalid or forged token proceeds as anonymous. Such endpoints must call getAuthData() and treat a null result as anonymous, identify callers only from that auth data, and use auth: true wherever a valid user is required.

Auth with Endpoints

Endpoint ConfigRequest Has AuthResult

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)

auth: true only authenticates the caller; it does not authorize them. Handlers must check the caller role before admin actions and the caller ownership of any requested resource, and return a permission denied error when the check fails.

Service-to-Service Auth Propagation

Auth data automatically propagates to internal service calls:

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 };
  }
);

In real code, compare the order owner with the caller user ID from the auth data before returning the order, and return a permission denied error when they differ, so callers can only read their own orders.

Overriding Auth Data

You can explicitly override auth data when making service-to-service calls:

import { user } from "~encore/clients";

// Override auth data for this specific call
const orderUser = await user.getProfile(
  {},
  { authData: { userID: "test-user-123", email: "test@example.com", role: "user" } }
);

An auth data override replaces the result of the auth handler and acts as the given user, so it amounts to impersonation. Use it only in tests or in reviewed internal flows, log each use, build the override only from already verified auth data, and keep elevated roles out of it in production.

Common Auth Patterns

JWT Token Validation

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;
  }
}

Also pass the expected issuer, the expected audience and an allowed list of signing algorithms to the verify call, and require an expiry claim. Use a long, high entropy random secret, or asymmetric keys. Install jose from the npm registry pinned to a maintained major version, and commit the lockfile.

API Key Authentication

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,
    };
  }
);

Store only a hash of each API key (for example a keyed hash using a server side secret), look the presented key up by its hash, and compare in constant time. Support key prefixes, scoping, rotation and revocation. Strip and check the expected scheme prefix from the header and reject malformed values before the database lookup.

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,
    };
  }
);

Set session cookies as http only and secure, with the same site attribute set to Lax or Strict. Endpoints that change state must require a cross site request forgery token or an Origin or Referer check, and GET endpoints must only read state.

Testing with Auth

Mock authentication in tests using Vitest:

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: "test@example.com",
      role: "user",
    }));

    const profile = await getProfile();
    expect(profile.email).toBe("test@example.com");

    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
  • Use the exclamation mark assertion on getAuthData() only in auth: true endpoints; elsewhere, check the result for null and handle the anonymous case
  • 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

Install

Install with chainctl
chainctl skill install public/encoredev/auth:latest
Get started with chainctl
View source

Harden your own skills

Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.

Provenance

Upstream sourcehttps://github.com/encoredev/skills
Upstream commit023e613c4ff8c9152830264d002b09fba527b9a1
OCI reference

skills.cgr.dev/public/github.com/encoredev/encore/auth:latest

License

Apache-2.0

Files

HARDENING.md

LICENSE

SKILL.md


The trusted source for open source

Talk to an expert
PrivacyTerms

Product

Chainguard ContainersChainguard LibrariesChainguard VMsChainguard OS PackagesChainguard ActionsChainguard Agent SkillsIntegrationsPricing
© 2026 Chainguard, Inc. All Rights Reserved.
Chainguard® and the Chainguard logo are registered trademarks of Chainguard, Inc. in the United States and/or other countries.
The other respective trademarks mentioned on this page are owned by the respective companies and use of them does not imply any affiliation or endorsement.