Data integration
The module implements a JWT-based session so authorization is stateless. Data needs to be persisted to have control over sessions and provide user-related functionalities (e.g. registration). For that, the module offers two options: Frontend-only with backend API or Full-stack with data adapter.
Frontend-only
The module provides a frontend-only option that turns off the built-in backend by excluding the server's handlers, utilities, and middleware and permits users to bring their own. The provided backend can be internal, meaning as part of the application, or external.
This feature does not affect the frontend implementation meaning the same APIs and benefits (auto-redirection, auto-refresh of token) as the full-stack implementation.
The specification for the backend APIs is described here.
To enable this feature, these config options should be set:
backendEnabled
set tofalse
.backendBaseUrl
set to/
for internal Backend.
Full-stack
The full-stack option requires a data source adapter. An adapter is an object with methods for reading and writing the required data models. You can use a built-in adapter or create your own.
To create a custom adapter two models are required:
User
to store a user's data.Session
to store a session's data for a specific user.
interface User {
id: UserId;
name: string;
email: string;
picture: string;
role: string;
provider: string;
verified: boolean;
createdAt: Date;
updatedAt: Date;
suspended?: boolean | null;
password?: string | null;
requestedPasswordReset?: boolean | null;
}
interface Session {
id: SessionId;
uid: string;
userAgent: string | null;
createdAt: Date;
updatedAt: Date;
userId: User["id"];
}
By default, the id
fields are of type string
. To change it to number
, the UserId
and SessionId
types need to be overwritten:
declare module "#auth_adapter" {
type UserId = number; // Change User ID type to `number`
type SessionId = number; // Change Session ID type to `number`
}
To define a new adapter, the defineAdapter
utility is provided. The expected argument is the data source, the API you use to interact with your data (e.g. Prisma Client).
interface Adapter {
name: string;
source: Source;
user: {
findById: (id: User["id"]) => Promise<User | null>;
findByEmail: (email: User["email"]) => Promise<User | null>;
create: (input: UserCreateInput) => Promise<UserCreateOutput>;
update: (id: User["id"], input: UserUpdateInput) => Promise<void>;
};
session: {
findById: (
id: Session["id"],
userId: User["id"]
) => Promise<Session | null>;
findManyByUserId: (id: User["id"]) => Promise<Session[]>;
create: (input: SessionCreateInput) => Promise<SessionCreateOutput>;
update: (id: Session["id"], input: SessionUpdateInput) => Promise<void>;
delete: (id: Session["id"], userId: User["id"]) => Promise<void>;
deleteManyByUserId: (
userId: User["id"],
excludeId?: Session["id"] // The current session's id, it should not be deleted.
) => Promise<void>;
};
}
To integrate your adapter within your application, the setEventContext
utility is provided. It extends event.context
with the auth
property that contains the access token's payload on auth.data
and the adapter instance on auth.adapter
.
import { defineAdapter, setEventContext } from "#auth_utils";
const useAdapter = defineAdapter<SourceType>((source)=> {/* */});
export default defineNitroPlugin((nitroApp) => {
const adapter = useAdapter()
nitroApp.hooks.hook("request", (event) => setEventContext(event, adapter));
}
});
On your event handlers, the data source used by the adapter can be accessed on event.context.auth.adapter.source
. Note that it needs to be manually typed:
declare module "#auth_adapter" {
type Source = SourceType;
}