Skip to content

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.

HttpApiMiddleware.Service declares the middleware. The type parameter object describes what it provides and requires; the options describe its security schemes and error type.

api/Authorization.ts
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 consume
export class CurrentUser extends Context.Service<CurrentUser, User>()(
"acme/HttpApi/Authorization/CurrentUser"
) {}
// The error this middleware can raise, with its HTTP status
export 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
}) {}

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")
{}

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).

server/Authorization.ts
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 user

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.

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.