Type-safe, plugin-first authentication core for TypeScript applications.
- Plugin architecture for auth methods and domain capabilities.
- Strongly typed register/authenticate payloads inferred from enabled plugins.
- Path-based issue model (
{ message, path }) for field-level error mapping. - Built-in methods: password, email OTP, magic link, OAuth2, passkey.
- Built-in domain plugins: two-factor auth (TOTP/recovery), organizations/RBAC.
- Framework-agnostic core (works with Next.js, SvelteKit, Workers, Node servers).
npm install @oglofus/authpnpm add @oglofus/authbun add @oglofus/authOptional for app-level integrations:
arcticfor OAuth providers in your app code.@oslojs/otpif you need direct OTP utilities in your app (the library already uses it internally for TOTP).
import {
OglofusAuth,
passwordPlugin,
type PasswordCredentialAdapter,
type SessionAdapter,
type UserAdapter,
type UserBase,
} from "@oglofus/auth";
interface AppUser extends UserBase {
given_name: string;
family_name: string;
}
const users: UserAdapter<AppUser> = /* your adapter */;
const sessions: SessionAdapter = /* your adapter */;
const credentials: PasswordCredentialAdapter = /* your adapter */;
const auth = new OglofusAuth({
adapters: { users, sessions },
plugins: [
passwordPlugin<AppUser, "given_name" | "family_name">({
requiredProfileFields: ["given_name", "family_name"] as const,
credentials,
}),
] as const,
validateConfigOnStart: true,
});
const registered = await auth.register({
method: "password",
email: "nikos@example.com",
password: "super-secret",
given_name: "Nikos",
family_name: "Gram",
});
const loggedIn = await auth.authenticate({
method: "password",
email: "nikos@example.com",
password: "super-secret",
});OglofusAuth exposes:
discover(input, request?)register(input, request?)authenticate(input, request?)method(pluginMethod)for plugin-specific APIsverifySecondFactor(input, request?)completeProfile(input, request?)setActiveOrganization(sessionId, organizationId, request?)validateSession(sessionId, request?)signOut(sessionId, request?)
All operations return structured results.
if (!result.ok) {
console.log(result.error.code); // e.g. "INVALID_INPUT"
console.log(result.error.status); // HTTP-friendly status code
console.log(result.issues); // path-based issues
}Issue format:
type Issue = {
message: string;
path?: ReadonlyArray<PropertyKey | { key: PropertyKey } | { index: number }>;
};You can build issues with helpers:
import { createIssue, createIssueFactory } from "@oglofus/auth";
const issue = createIssueFactory<{ email: string; profile: unknown }>([
"email",
"profile",
] as const);
issue.email("Email is required");
issue.$path(
["profile", { key: "addresses" }, { index: 0 }, "city"],
"City is required",
);
createIssue("Generic failure");- Method:
"password" - Register + authenticate supported.
- Config:
requiredProfileFields,credentialsadapter.
- Method:
"email_otp" - Two-step flow with plugin API:
auth.method("email_otp").request({ email })auth.authenticate(...)orauth.register(...)withchallengeId+code
- Method:
"magic_link" - Two-step flow with plugin API:
auth.method("magic_link").request({ email })auth.authenticate(...)orauth.register(...)withtoken
- Method:
"oauth2" - Uses provider clients with
validateAuthorizationCode(...)(Arctic-compatible). - Supports profile completion when required fields are missing.
import { Google } from "arctic";
import { oauth2Plugin } from "@oglofus/auth";
const google = new Google(process.env.GOOGLE_CLIENT_ID!, process.env.GOOGLE_CLIENT_SECRET!, process.env.GOOGLE_REDIRECT_URI!);
oauth2Plugin<AppUser, "google", "given_name" | "family_name">({
providers: {
google: {
client: google,
resolveProfile: async ({ tokens }) => {
const res = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
headers: { Authorization: `Bearer ${tokens.accessToken()}` },
});
const p = await res.json() as {
sub: string;
email?: string;
email_verified?: boolean;
given_name?: string;
family_name?: string;
};
return {
providerUserId: p.sub,
email: p.email,
emailVerified: p.email_verified,
profile: {
given_name: p.given_name ?? "",
family_name: p.family_name ?? "",
},
};
},
// pkceRequired defaults to true
},
},
accounts: /* OAuth2AccountAdapter<"google"> */,
requiredProfileFields: ["given_name", "family_name"] as const,
});
const result = await auth.authenticate({
method: "oauth2",
provider: "google",
authorizationCode: "code-from-callback",
redirectUri: process.env.GOOGLE_REDIRECT_URI!,
codeVerifier: "pkce-code-verifier",
});- Method:
"passkey" - Register + authenticate supported.
- Config:
requiredProfileFields,passkeysadapter.
- Method:
"two_factor" - Adds post-primary verification (
TWO_FACTOR_REQUIRED). - Uses
@oslojs/otpinternally for TOTP verification and enrollment URI generation. - Plugin API:
beginTotpEnrollment(userId)confirmTotpEnrollment({ enrollmentId, code })regenerateRecoveryCodes(userId)
- Method:
"organizations" - Multi-tenant orgs, memberships, role inheritance, feature/limit entitlements, invites.
- Validates role topology on startup (default role, owner role presence, inheritance cycles).
Use discover(...) to support login/register routing logic before full auth:
privatemode: generic non-enumerating response.explicitmode: returns account-aware actions (continue_login,redirect_register,redirect_login).
explicit mode requires an identity adapter.
See ready-to-copy integrations:
examples/nextjs-passwordexamples/sveltekit-email-otpexamples/oauth2-google-arcticexamples/two-factor-totp-oslo
pnpm run typecheck
pnpm run test
pnpm run build- Build:
pnpm run build(outputs todist/) - TypeScript config:
tsconfig.json
ISC License. See the LICENSE file for details.