Skip to content
fossyl

Authentication

Authentication in fossyl is a function that receives request headers and returns a branded Authentication object via authWrapper(). The return type flows into your handler — no explicit generics needed.

Authentication functions must return a Promise. This allows for async operations — JWT verification, database lookups, OAuth token exchange, and more.

export const authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication> = async (headersRecord<string, string>: Record<string, string>) => {
const userIdstring = headersRecord<string, string>["x-user-id"];
if (!userIdstring) {
throw new AuthenticationError("Unauthorized");
}
return authWrapper<T>(auth: T) => T & Authentication({ userIdstring });
};

The authWrapper() function brands the return value so the type system can distinguish authentication data from body data. This is how fossyl knows to pass auth data to the correct handler parameter position.

Look up users in your database using getDb() from the Express adapter context:

export const getTodoRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api/todos">
.createEndpoint<Path extends `/api/todos${string}`>( path: Path, ) => Endpoint<Path, true>("/api/todos/:id")
.authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>(authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>)
.get<Response extends ResponseData>( handler: ( parameters: { query: { q: string; limit?: number; offset?: number } & { readonly __kind: "query" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route((paramsRecord<string, string | undefined>) => (_auth{ userId: string } & Authentication & { readonly __kind: "auth" }) => async () => {
const todoTodoRow = { id: number title: string completed: number created_at: string } = await todoService.getTodoRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(Number(paramsRecord<string, string | undefined>.url{ id: string } & { readonly __kind: "url" }.idnumber));
return { typeName: "Todo" as const, ...todoTodoRow = { id: number title: string completed: number created_at: string } };
});

The database client is available because the database adapter provides it via AsyncLocalStorage. This works automatically when you configure a database adapter on the framework adapter.

Compose authenticators for role-based access. Since authenticators are just functions, you can create higher-order authenticators that check roles:

export const createTodoRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api/todos">
.createEndpoint<Path extends `/api/todos${string}`>( path: Path, ) => Endpoint<Path, true>("/api/todos")
.authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>(authenticator( headers: Record<string, string>, ) => Promise<{ userId: string } & Authentication>)
.validator<RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route }(createTodoValidator( data: unknown, params?: util.InexactPartial<ParseParams>, ) => { title: string })
.post<Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => ( body: { title: string } & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route((_auth{ userId: string } & Authentication & { readonly __kind: "auth" }) => (body{ title: string } & { readonly __kind: "body" }) => async () => {
const todoTodoRow = { id: number title: string completed: number created_at: string } = await todoService.createTodoRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean }(body{ title: string } & { readonly __kind: "body" }.titlestring);
return { typeName: "Todo" as const, ...todoTodoRow = { id: number title: string completed: number created_at: string } };
});

Builder Configurations with Authentication

Section titled “Builder Configurations with Authentication”

Authentication is available on these builder configurations:

ConfigurationAuthUse Case
Authenticated (.authenticator().get())RequiredProtected GET/DELETE
Full (.authenticator().validator().post())RequiredProtected POST/PUT
Authenticated + Paginated (.paginate().authenticator().get())RequiredProtected paginated collections

Throw AuthenticationError from your authenticator to return a 401 response:

import { AuthenticationError } from '@fossyl/express';
// The adapter catches this and returns:
// {
// success: "false",
// error: { code: 1002, message: "Invalid token" }
// }

The Express adapter’s error middleware handles AuthenticationError automatically, returning a standardized error response with error code 1002.