Scope is the way Kilpi stores values scoped to a single request.
It is used to store e.g. the current onUnauthorized
handler set in Kilpi.onUnauthorized
, as well as to cache the current subject.
Provide an explicit scope
You can provide an explicit scope using either Kilpi.runInScope
(to run a function) or Kilpi.scoped
(to wrap a function) as follows.
Kilpi.onUnauthorized(...); // Will not work, logs a warning
// Using Kilpi.runInScope (runs the given function in a scope)const result = await Kilpi.runInScope(async () => { Kilpi.onUnauthorized(...); // This works ...})
// Using Kilpi.scoped (returns a function wrapped in a scope)export const POST = Kilpi.scoped(async (req) => { Kilpi.onUnauthorized(...); // This works ...})
If possible, you should provide scope for each request using a middleware or similar.
app.use(async (_, next) => { return await Kilpi.runInScope(async () => { return await next(); // Run handler in Kilpi scope });})
Kilpi.runInScope
andKilpi.scoped
useAsyncLocalStorage
under the hood.
Implicit scope
Plugins can automatically provide an implicit scope in certain contexts, without having to call Kilpi.runInScope
or Kilpi.scoped
explicitly.
For example, the React Server Components Plugin automatically provides a scope for React Server Components.
const Kilpi = createKilpi({ // ... plugins: [ReactServerComponentPlugin()],})
export async function Page() { Kilpi.onUnauthorized(() => redirect("/")); // Automatically works Kilpi.authorize("authed");}
This works by using the Kilpi.hooks.onRequestScope
hook to automatically create a scope for each request, when no explicit scope is available.
Not able to provide a scope?
If you are unable to provide a request-level scope for any reason, you can use the following workarounds.
Kilpi.authorize
and Kilpi.unauthorized
You can’t set up request-level error handlers using `Kilpi To use Kilpi.authorize without a scope, you can…
- Use the default error handler provided to
createKilpi
to handle unauthorized requests, and avoid using request-level error handlers withKilpi.onUnauthorized
. - Avoid APIs that throw and instead use manual APIs (such as
Kilpi.isAuthorized
andKilpi.getAuthorization
instead ofKilpi.authorize
).
Background and motivations
The scope API was designed to
-
Reduce boilerplate
With scope, you optimally only have to call
Kilpi.onUnauthorized
once, and all subsequent authorizations (in nested queries, components, …) will automatically use the same handler. -
Enable caching
The scope is used to cache e.g. the subject for the current request.
-
Enable protected queries
Without a “global”
onUnauthorized
handler, you would have to define what happens when a protected query authorization fails. The same query should throw aHTTPForbiddenError
in one context and redirect to the login page in another.
The primary trade-off of this approach is the complexity of having to manually provide a scope or use a plugin adds complexity to installing the library.