Middleware & auth
HttpApi middleware is a typed, schema-aware layer that runs around endpoints.
Unlike raw HTTP middleware it is part of the API definition, so it can:
- declare security schemes (bearer, API key, basic) that are decoded for you and documented in OpenAPI,
- provide services (such as the current user) to downstream middleware and handlers, and
- declare the errors it may raise, which then appear in the endpoint’s error channel and in the generated client.
Middleware is split into a definition (shared with clients) and an implementation (server-only), exactly like the API itself.
Defining the middleware
Section titled “Defining the middleware”HttpApiMiddleware.Service declares the middleware. The type parameter object
describes what it provides and requires; the options describe its security
schemes and error type.
import { Context, Schema } from "effect"import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi"import type { User } from "../domain/User.ts"
// The service this middleware injects for downstream handlers to consumeexport class CurrentUser extends Context.Service<CurrentUser, User>()( "acme/HttpApi/Authorization/CurrentUser") {}
// The error this middleware can raise, with its HTTP statusexport class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()( "Unauthorized", { message: Schema.String }, { httpApiStatus: 401 }) {}
export class Authorization extends HttpApiMiddleware.Service<Authorization, { // Services made available to endpoints/middleware that run after this one provides: CurrentUser // Services this middleware needs from earlier middleware (none here) requires: never}>()("acme/HttpApi/Authorization", { // Clients must also implement this middleware (e.g. to attach a token) requiredForClient: true, // Security schemes power OpenAPI docs *and* decode credentials for you security: { bearer: HttpApiSecurity.bearer }, // Errors this middleware may fail with error: Unauthorized}) {}Applying middleware
Section titled “Applying middleware”Attach middleware to a whole group with .middleware(...), or to a single
endpoint. Group-level is the common case for auth:
// api/Users.ts (excerpt)import { HttpApiGroup } from "effect/unstable/httpapi"import { Authorization } from "./Authorization.ts"
class UsersApiGroup extends HttpApiGroup.make("users") .add(/* ...endpoints... */) // Every endpoint in this group now requires authorization, and `CurrentUser` // becomes available to each handler. .middleware(Authorization) .prefix("/users"){}Implementing the middleware (server)
Section titled “Implementing the middleware (server)”The implementation is a Layer providing the Authorization service. Because
the definition declared a bearer security scheme, the implementation receives
the already-decoded credential. Validate it and use Effect.provideService to
inject the resolved CurrentUser into the continuation (httpEffect).
import { Effect, Layer, Redacted } from "effect"import { Authorization, CurrentUser, Unauthorized } from "../api/Authorization.ts"import { User, UserId } from "../domain/User.ts"
export const AuthorizationLayer = Layer.effect( Authorization, Effect.gen(function*() { // Resolve middleware dependencies once (a DB, an auth provider, ...) yield* Effect.logInfo("Starting Authorization middleware")
return Authorization.of({ // One handler per declared security scheme. `credential` is decoded from // the Authorization header and wrapped in Redacted so it never logs. bearer: Effect.fn(function*(httpEffect, { credential }) { const token = Redacted.value(credential) if (token !== "dev-token") { // Failing here short-circuits the request with a 401 return yield* new Unauthorized({ message: "Missing or invalid bearer token" }) }
// Provide CurrentUser to everything downstream: other middleware and // the endpoint handler. return yield* Effect.provideService( httpEffect, CurrentUser, new User({ id: UserId.make(1), name: "Dev User", email: "dev@acme.com" }) ) }) }) }))The handler from the previous page can now read the injected user:
// inside HttpApiBuilder.group(Api, "users", ...).handle("me", () => CurrentUser) // returns the injected current userSecurity schemes
Section titled “Security schemes”HttpApiSecurity provides the standard schemes, each of which produces a decoded
credential and a matching OpenAPI security definition:
import { HttpApiSecurity } from "effect/unstable/httpapi"
HttpApiSecurity.bearer // Authorization: Bearer <token>HttpApiSecurity.apiKey({ in: "header", key: "x-api-key" })HttpApiSecurity.basic // Authorization: Basic ...For bearer and apiKey the credential is a Redacted<string>; for basic it
is a { username, password } credentials object.
Client-side middleware
Section titled “Client-side middleware”When a middleware is requiredForClient: true, the generated client must also
implement it — typically to attach the credential to outgoing requests. You
provide that with HttpApiMiddleware.layerClient:
import { Effect } from "effect"import { HttpClientRequest } from "effect/unstable/http"import { HttpApiMiddleware } from "effect/unstable/httpapi"import { Authorization } from "../api/Authorization.ts"
export const AuthorizationClient = HttpApiMiddleware.layerClient( Authorization, Effect.fn(function*({ next, request }) { // Modify the request, then pass it down the chain. Here we attach a token. return yield* next(HttpClientRequest.bearerToken(request, "dev-token")) }))This client layer is provided when constructing the client — see Serving & clients for the full wiring.