Complete application identity
Add multi-provider login, sessions, route protection, recent-auth checks, organizations, API keys, MFA, passwordless flows, SSO, OIDC, SAML, and WebAuthn.
A comprehensive TypeScript-based authentication system built for Elysia applications. Complete OAuth 2.0 solution with optional OpenID Connect capabilities and end-to-end type safety.
View on GitHubMulti-Provider Support
78 OAuth 2.0 providers with OpenID Connect support
Type-Safe
Full TypeScript support with comprehensive type definitions
Session Management
Built-in session handling with automatic expiration
Token Management
Automatic token refresh and revocation support
Route Protection
Easy-to-use route protection with typed callbacks
Event Hooks
Customizable handlers for all authentication flows
PKCE Support
Automatic PKCE implementation for supported providers
Security
Secure cookie handling and CSRF protection built-in
/oauth2/auth/:providerbun install @absolutejs/authimport { Elysia } from 'elysia';
import { absoluteAuth } from '@absolutejs/auth';
import { getEnv } from '@absolutejs/absolute';
const app = new Elysia()
// No `<User>` type argument needed — `User` is INFERRED from `getUser`.
.use(absoluteAuth({
// `getUser` resolves your app user from a session subject. It is required,
// and it is the single source of truth for `User`: the type is inferred
// from what you return here, and every other user-typed callback
// (createUser, getUserByEmail, onCallbackSuccess, …) is checked against
// that same shape — so a mismatched user is a compile error, not a bug.
getUser: (sub) => findUserBySub(sub),
providersConfiguration: {
google: {
credentials: {
clientId: getEnv('GOOGLE_CLIENT_ID'),
clientSecret: getEnv('GOOGLE_CLIENT_SECRET'),
redirectUri: 'http://localhost:3000/oauth2/callback'
},
scope: ['openid', 'profile', 'email']
}
}
}))
.listen(3000);getUser is the one required hook, and it carries its weight: it resolves your app user from a session subject and is the single source of truth for your User type. The type is inferred from what getUser returns — so you rarely need a <User> argument — and every other user-typed callback (createUser, getUserByEmail, onCallbackSuccess) is checked against that same shape. A user that doesn't line up is a compile error, not a runtime surprise.
The protectRoute helper function protects routes that require authentication. It accepts two callbacks with fully typed parameters: the user object matches your exact user shape, and the error object is one of the specific authentication errors, giving you complete type safety for both success and failure paths.
app.get('/protected', ({ status, protectRoute }) =>
protectRoute(
(user) => {
return `Hello, ${user.name}!`;
},
(error) => status(error.code, error.message)
)
);When you use the Absolute Auth plugin, it automatically creates all the authentication routes you need. You do not implement your own login, status, or sign-out routes, you simply call the ones already provided.
// Option 1: Use an anchor element
<a href="/oauth2/google/authorization">Sign in with Google</a>
// Option 2: Redirect the user to the provider's authorization URL
redirect('/oauth2/google/authorization');This triggers the built-in authorization route, which handles:
import { server } from '/src/frontend/utils/edenTreaty';
const { data, error } = await server.oauth2.status.get();
if (error) {
console.error('Not authenticated:', error);
} else {
console.log('User:', data.user);
}import { server } from '/src/frontend/utils/edenTreaty';
const { data, error } = await server.oauth2.signout.delete();
if (error) {
console.error('Signout failed:', error);
} else {
console.log('Successfully signed out');
}This calls the built-in sign-out route, which:
The library automatically creates the following routes:
| Route | Method | Description |
|---|---|---|
/oauth2/:provider/authorization | GET | Initiate OAuth flow with specified provider |
/oauth2/callback | GET | Handle OAuth callback and token exchange |
/oauth2/status | GET | Check current user authentication status |
/oauth2/profile | GET | Fetch user profile from OAuth provider |
/oauth2/tokens | POST | Refresh access token using refresh token |
/oauth2/revocation | POST | Revoke access or refresh token |
/oauth2/signout | DELETE | Sign out user and clear session |
Absolute Auth provides automatic session management with configurable lifetimes and cleanup. Sessions are automatically cleaned up at regular intervals, and you can also trigger cleanup manually using the derived cleanupSessions function.
Configure session behavior using millisecond-based options:
.use(await absoluteAuth<User>({
providersConfiguration: { /* ... */ },
// Session lifetime configuration
sessionDurationMs: 86400000, // 24 hours (default)
unregisteredSessionDurationMs: 3600000, // 1 hour (default)
cleanupIntervalMs: 300000, // 5 minutes (default)
maxSessions: 5, // Max sessions per user (default)
// Called when sessions are cleaned up
onSessionCleanup: async ({ removedSessions, removedUnregisteredSessions }) => {
console.log(`Cleaned up ${removedSessions.size} expired sessions`);
}
}))The cleanup process:
When you use absoluteAuth, a cleanupSessions function is derived and made available in all your route handlers. This allows you to trigger cleanup programmatically:
// The cleanupSessions function is derived from the auth middleware
// and available in all route handlers
app.post('/admin/cleanup', async ({ cleanupSessions }) => {
// Manually trigger session cleanup
await cleanupSessions();
return { message: 'Sessions cleaned up' };
});
// You can also use it in scheduled tasks
app.get('/health', async ({ cleanupSessions }) => {
// Cleanup runs automatically via cleanupIntervalMs,
// but can be triggered manually if needed
await cleanupSessions();
return { status: 'healthy' };
})The derived function follows Elysia's plugin pattern: it captures the session configuration from when absoluteAuth was initialized and provides a simple async function you can call from any route.
Absolute Auth does not provide database adapters. Instead, it exposes hooks throughout the OAuth lifecycle, allowing you to integrate any persistence layer or user model. These hooks provide full control over user creation, updates, and session handling while keeping the OAuth flow standardized and database-agnostic.
Called after the provider returns and tokens are exchanged. Use it to load or create users via instantiateUserSession:
onCallbackSuccess: async ({ authProvider, tokenResponse, session, userSessionId }) =>
instantiateUserSession({
authProvider,
tokenResponse,
session,
userSessionId,
getUser: async (userIdentity) => {
// Find user in your database
return await db.users.findByAuthSub(userIdentity.sub);
},
onNewUser: async (userIdentity) => {
// Create new user in your database
return await db.users.create({
authSub: userIdentity.sub,
email: userIdentity.email,
name: userIdentity.name
});
}
})Customize the route paths for all authentication endpoints:
| Prop | Default | Description |
|---|---|---|
authorizeRoute | /oauth2/:provider/authorization | Custom authorization route path |
callbackRoute | /oauth2/callback | Custom callback route path |
statusRoute | /oauth2/status | Custom status check route path |
signoutRoute | /oauth2/signout | Custom sign-out route path |
profileRoute | /oauth2/profile | Custom profile fetch route path |
refreshRoute | /oauth2/tokens | Custom token refresh route path |
revokeRoute | /oauth2/revocation | Custom token revocation route path |
Hook into each stage of the OAuth flow for custom behavior:
| Hook | Description |
|---|---|
onAuthorizeSuccess | Called before redirecting to provider |
onAuthorizeError | Called when authorization URL generation fails |
onCallbackSuccess | Called after successful token exchange |
onCallbackError | Called when callback/token exchange fails |
onProfileSuccess | Called after successful profile fetch |
onProfileError | Called when profile fetch fails |
onStatus | Called when checking user session status |
onRefreshSuccess | Called after successful token refresh |
onRefreshError | Called when token refresh fails |
onRevocationSuccess | Called after successful token revocation |
onRevocationError | Called when token revocation fails |
onSignOut | Called before session destruction |
onSessionCleanup | Called when expired sessions are removed during cleanup |
These playbooks show where this package fits, how to verify the combined system, and what changes before production.
Current package surface
Import surface · click to copy
const auth: <UserType>(configuration: AuthConfig<UserType>) => Promise<Elysia<"", {
decorator: {};
store: {
session: import("./types").SessionRecord<UserType>;
unregisteredSession: import("./types").UnregisteredSessionRecord;
};
derive: ({
readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
readonly code: "Bad Request";
readonly message: "Cookies are missing";
} | {
readonly code: "Unauthorized";
readonly message: "User is not authenticated";
}) => AuthFailReturn) | undefined) => Promise<import("elysia").ElysiaCustomStatusResponse<"Bad Request", "Cookies are missing", 400> | import("elysia").ElysiaCustomStatusResponse<"Unauthorized", "User is not authenticated", 401> | AuthReturn | NonNullable<AuthFailReturn>>;
} & {
readonly requireRecentAuth: <AuthReturn, AuthFailReturn_1>(maxAgeMs: number, handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
readonly code: "Unauthorized";
readonly message: "Recent aut@absolutejs/authOutcomes
Add multi-provider login, sessions, route protection, recent-auth checks, organizations, API keys, MFA, passwordless flows, SSO, OIDC, SAML, and WebAuthn.
Bind authenticated users, delegated AI agents, authorization details, permissions, audit evidence, and credential vaults to one reusable request context.
Hardening checklist
Follow in order