| title |
Implement API Authentication |
| id |
api-authentication |
| skillLevel |
intermediate |
| applicationPatternId |
building-apis |
| summary |
Add JWT or session-based authentication to protect your API endpoints. |
| tags |
building-apis |
authentication |
jwt |
security |
|
| rule |
| description |
Use middleware to validate authentication tokens before handling requests. |
|
| author |
PaulJPhilp |
| related |
api-middleware |
handle-api-errors |
|
| lessonOrder |
5 |
Implement authentication as middleware that validates tokens and provides user context to route handlers.
Authentication protects your API:
- Identity verification - Know who's making requests
- Access control - Limit what users can do
- Audit trail - Track who did what
- Rate limiting - Per-user limits
import { Effect, Context, Layer, Data } from "effect"
import { HttpServer, HttpServerRequest, HttpServerResponse } from "@effect/platform"
// ============================================
// 1. Define authentication types
// ============================================
interface User {
readonly id: string
readonly email: string
readonly roles: ReadonlyArray<string>
}
class AuthenticatedUser extends Context.Tag("AuthenticatedUser")<
AuthenticatedUser,
User
>() {}
class UnauthorizedError extends Data.TaggedError("UnauthorizedError")<{
readonly reason: string
}> {}
class ForbiddenError extends Data.TaggedError("ForbiddenError")<{
readonly requiredRole: string
}> {}
// ============================================
// 2. JWT validation service
// ============================================
interface JwtService {
readonly verify: (token: string) => Effect.Effect<User, UnauthorizedError>
}
class Jwt extends Context.Tag("Jwt")<Jwt, JwtService>() {}
const JwtLive = Layer.succeed(Jwt, {
verify: (token) =>
Effect.gen(function* () {
// In production: use a real JWT library
if (!token || token === "invalid") {
return yield* Effect.fail(new UnauthorizedError({
reason: "Invalid or expired token"
}))
}
// Decode token (simplified)
if (token.startsWith("user-")) {
return {
id: token.replace("user-", ""),
email: "user@example.com",
roles: ["user"],
}
}
if (token.startsWith("admin-")) {
return {
id: token.replace("admin-", ""),
email: "admin@example.com",
roles: ["user", "admin"],
}
}
return yield* Effect.fail(new UnauthorizedError({
reason: "Malformed token"
}))
}),
})
// ============================================
// 3. Authentication middleware
// ============================================
const extractBearerToken = (header: string | undefined): string | null => {
if (!header?.startsWith("Bearer ")) return null
return header.slice(7)
}
const authenticate = <A, E, R>(
handler: Effect.Effect<A, E, R | AuthenticatedUser>
): Effect.Effect<A, E | UnauthorizedError, R | Jwt | HttpServerRequest.HttpServerRequest> =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
const jwt = yield* Jwt
const authHeader = request.headers["authorization"]
const token = extractBearerToken(authHeader)
if (!token) {
return yield* Effect.fail(new UnauthorizedError({
reason: "Missing Authorization header"
}))
}
const user = yield* jwt.verify(token)
return yield* handler.pipe(
Effect.provideService(AuthenticatedUser, user)
)
})
// ============================================
// 4. Role-based authorization
// ============================================
const requireRole = (role: string) =>
<A, E, R>(handler: Effect.Effect<A, E, R | AuthenticatedUser>) =>
Effect.gen(function* () {
const user = yield* AuthenticatedUser
if (!user.roles.includes(role)) {
return yield* Effect.fail(new ForbiddenError({ requiredRole: role }))
}
return yield* handler
})
// ============================================
// 5. Protected routes
// ============================================
const getProfile = authenticate(
Effect.gen(function* () {
const user = yield* AuthenticatedUser
return HttpServerResponse.json({
id: user.id,
email: user.email,
roles: user.roles,
})
})
)
const adminOnly = authenticate(
requireRole("admin")(
Effect.gen(function* () {
const user = yield* AuthenticatedUser
return HttpServerResponse.json({
message: `Welcome admin ${user.email}`,
users: ["user1", "user2", "user3"],
})
})
)
)
// ============================================
// 6. Error handling
// ============================================
const handleAuthErrors = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
effect.pipe(
Effect.catchTag("UnauthorizedError", (e) =>
Effect.succeed(
HttpServerResponse.json({ error: e.reason }, { status: 401 })
)
),
Effect.catchTag("ForbiddenError", (e) =>
Effect.succeed(
HttpServerResponse.json(
{ error: `Requires role: ${e.requiredRole}` },
{ status: 403 }
)
)
)
)
Request → Extract Token → Verify JWT → Provide User → Handler
↓ ↓
401 401/403
| Type |
Storage |
Best For |
| JWT |
Header |
Stateless APIs |
| Session |
Cookie |
Web apps |
| API Key |
Header |
Service-to-service |
- Use HTTPS - Tokens in plain HTTP are visible
- Short expiry - JWTs should expire quickly
- Refresh tokens - For long sessions
- Validate claims - Check issuer, audience, expiry
- Log auth events - Track login attempts