Authorizing


Learn how to authorize and protect actions, pages, mutations, and more in your application.

Authorization APIs

Using Kilpi, there are several APIs for performing authorizations. They can all be used to protect your application, with slightly different behaviour.

Kilpi.isAuthorized

The simplest way to check for authorizations is to use Kilpi.isAuthorized. It returns a Promise<boolean> indicating whether the user is authorized or not. Pass it the policy key and a resource, if the policy requires one.

Unlike with Kilpi.authorize, you must also handle the unauthorized case manually.

if (await Kilpi.isAuthorized("documents:create")) {
await createDocument();
} else {
// Handle unauthorized here...
}
if (await Kilpi.isAuthorized("documents:delete", document)) {
await deleteDocument(document.id);
} else {
// Handle unauthorized here...
}

Kilpi.authorize

In some cases, throwing on unauthorized is the simplest way to protect your application, e.g. to automatically always throw a 403 error when an authorization check fails.

Kilpi.authorize also takes in the policy key and optionally a resource if the policy requires one.

// Both will throw if not authorized
await Kilpi.authorize("documents:create");
await Kilpi.authorize("documents:delete", document);

Handling unauthorized errors

Instead of the default behaviour (throws a KilpiError.AuthorizationDenied error), setup error handling to automatically handle unauthorized errors.

Setup a global error handler

Start by setting up a global error handler in createKilpi.

export const Kilpi = createKilpi({
...,
settings: {
defaultOnUnauthorized(error) {
throw new HTTPException(403, error.message);
}
}
})
// Will throw a 403 error if not authorized
await Kilpi.authorize("documents:create");

Setup a handler for each request (Optional)

For more control over your error handling, you can setup a handler for each request using Kilpi.onUnauthorized.

export default async function CreateDocumentPage() {
// Any unauthorized error anywhere on this page should redirect to login
Kilpi.onUnauthorized((error) => {
redirect("/login"); // Redirect via throw, e.g. in Next.js
});
// Will redirect to login if not authorized
await Kilpi.authorize("documents:create");
// ...
}

Kilpi.getAuthorization

Similarly to Kilpi.isAuthorized, Kilpi.getAuthorization allows you to manually control your authorizations but returns an authorization object, with more data about the result of the authorization check.

For granted authorizations, it contains the narrowed down subject. For denied authorizations, it contains the error message.

const authorization = await Kilpi.getAuthorization("documents:create");
if (authorization.granted) {
const user = authorization.subject;
} else {
console.error("Unauthorized", authorization.error);
}

Kilpi.unauthorized

If you want to throw similarly as Kilpi.authorize and trigger the default or request-level error handler, you can call Kilpi.unauthorized. It works the same way as a failed Kilpi.authorize check.

const isAllowed = await Kilpi.isAuthorized("documents:create");
if (!isAllowed) {
Kilpi.unauthorized("Unauthorized"); // Throws
}
// Equivalent to
await Kilpi.authorize("documents:create");

Protecting queries

The above APIs can be used to protect all parts of your application, even queries.

Kilpi however offers an optional Kilpi.query API for creating protected queries. This API allows for co-locating your queries with the authorization logic. Read more below.

Protected Queries

Learn how to protect queries and mutations in your application.

Introducing the Protected Query Pattern for secure data access

Read the article on protected queries