Skip to content

Multi-Auth Strategies

You need different authentication strategies on different routes — some routes require Bearer tokens, some accept API keys, some are public, and some allow both authenticated and anonymous access.

Set router-level defaults and override per route. Setting authorization: false disables auth entirely for that route.

import { compose, authorization } from "@centralping/ergo";
// Protected route — Bearer auth
const protectedPipeline = compose(
[authorization({
strategies: [{
type: "Bearer",
authorizer: async (attributes, token) => {
const user = await verifyJwt(token);
return user
? { authorized: true, info: { user } }
: { authorized: false, info: { statusCode: 401 } };
},
}],
}), "auth"],
(req, res, acc) => ({
response: { body: { userId: acc.auth.user.id } },
}),
);
// Public route — no auth middleware in the pipeline
const publicPipeline = compose(
(req, res, acc) => ({
response: { body: { status: "healthy" } },
}),
);

Optional Auth (Authenticated or Anonymous)

Section titled “Optional Auth (Authenticated or Anonymous)”

authorization: false skips auth entirely — it does not attempt authentication. For truly optional auth (attempt auth, allow anonymous on failure), use a custom middleware wrapper:

import { compose, authorization } from "@centralping/ergo";
function optionalAuth(options) {
const authMiddleware = authorization(options);
return async (req, res, acc, responseAcc) => {
const result = await authMiddleware(req, res, acc, responseAcc);
if (result?.response?.statusCode) {
// Auth failed — clear the pipeline break and continue anonymous
return { value: { user: undefined } };
}
return result;
};
}
const pipeline = compose(
[optionalAuth({
strategies: [{
type: "Bearer",
authorizer: async (attributes, token) => {
const user = await verifyJwt(token);
return user
? { authorized: true, info: { user } }
: { authorized: false };
},
}],
}), "auth"],
(req, res, acc) => ({
response: {
body: {
greeting: acc.auth?.user
? `Hello, ${acc.auth.user.name}`
: "Hello, guest",
},
},
}),
);

Use a custom scheme type to authenticate via a non-standard header (e.g., X-API-Key). Custom schemes receive the raw credential string.

import { compose, authorization } from "@centralping/ergo";
const pipeline = compose(
[authorization({
strategies: [{
type: "ApiKey",
authorizer: async (attributes, credentials) => {
const key = await lookupApiKey(credentials);
return key
? { authorized: true, info: { client: key.clientId } }
: { authorized: false, info: { statusCode: 401 } };
},
}],
}), "auth"],
(req, res, acc) => ({
response: { body: { clientId: acc.auth.client } },
}),
);

The authorization middleware evaluates strategies in array order. It parses the Authorization header, extracts the scheme name, and matches it against strategy type values (case-insensitive). The first matching strategy’s authorizer is called.

SchemeAuthorizer SignatureDescription
Basic(attributes, username, password)Base64 decoded and split on : by the library
Bearer(attributes, token)Raw token string (no decoding)
Custom(attributes, credentials)Raw credential string for any non-standard scheme
PatternEffectUse Case
authorization: falseRemoves auth middleware from the pipeline entirelyHealth checks, public endpoints, login
Optional auth wrapperAttempts auth, continues with undefined on failureFeeds, previews, mixed-access endpoints

With authorization: false, no Authorization header is parsed and acc.auth is not populated. With the optional auth pattern, acc.auth contains user info when authenticated or { user: undefined } for anonymous access.

In ergo-router, each route config key is resolved against router-level defaults. A route value replaces the default entirely — setting authorization: { strategies: [basicStrategy] } on a route discards any strategies defined in defaults.authorization.

This allows a single defaults.authorization to protect the entire router while individual routes opt out (false) or override with different strategies.

See Config Resolution for the full resolution rules, value table, and how to extend defaults with spread.

For per-group auth using separate sub-routers instead of per-route overrides, see Sub-Routers.