Skip to main content

Authentication

Authentication in KoalaTs starts at the request boundary. You define which routes are public, which routes require a user, and how that user is resolved for the request.

Define The Security Configuration​

Keep the firewall rules in one place so the security policy is easy to review.

./src/config/security.ts
import type { SecurityConfig } from '@koala-ts/framework/Security';
import { userProvider } from './user-provider';

export const security: SecurityConfig = {
firewalls: [
{
pattern: '^/login',
security: false,
},
{
pattern: '^/api',
security: true,
provider: userProvider,
},
],
};

In that setup:

  • /login stays public
  • routes under /api require an authenticated user

If a request matches a protected firewall and no user can be resolved, KoalaTs throws 401 Authentication required.

Apply The Firewall​

Apply the firewall through globalMiddleware so it runs before your route handlers.

./src/config/koala.ts
import type { KoalaConfig } from '@koala-ts/framework';
import { firewall } from '@koala-ts/framework/Security';
import { security } from './security';

export const appConfig: KoalaConfig = {
globalMiddleware: [firewall(security)],
routes: [],
};

Resolve The Current User​

On protected routes, the firewall can call a provider to resolve the current user from the request.

./src/config/user-provider.ts
import type { UserProvider } from '@koala-ts/framework/Security';

export const userProvider: UserProvider = async request => {
if ('Bearer demo-token' !== request.headers.authorization) {
return undefined; // reject the request with 401 Authentication required
}

return {
id: 'user-1',
name: 'John Doe',
roles: ['ROLE_USER'],
};
};

The provider contract is simple:

  • return a user object to authenticate the request
  • return undefined to reject the request with 401 Authentication required

When a user is returned, KoalaTs assigns it to scope.user.

Firewall Rule Matching​

KoalaTs uses the first matching firewall rule. Order matters, so place specific rules before broader ones.

firewall({
firewalls: [
{
pattern: '^/api/public',
security: false,
},
{
pattern: '^/api',
security: true,
},
],
});

In that example, /api/public/health stays public because the more specific rule matches first.

Each firewall rule has three parts:

  • pattern: a regular expression tested against scope.request.path
  • security: whether the matching path requires authentication
  • provider: an optional async function that resolves a user from the request

Working With scope.user​

After the firewall authenticates a request, the resolved user is assigned to scope.user.

That lets downstream middleware and route handlers use the current user without repeating token parsing or lookup logic.

Testing Authenticated Requests​

In e2e tests, you can act as a specific user when creating the test agent.

import { createTestAgent } from '@koala-ts/framework';

const MyUser = {
identifier: 'user-1',
};

const agent = createTestAgent(appConfig, { actAs: MyUser });

Use a plain agent when you want to assert that a protected route returns 401:

const anonymousAgent = createTestAgent(appConfig);

This gives you two clear test paths:

  • unauthenticated requests should be rejected on protected routes
  • authenticated requests can be executed with actAs