Skip to content

Authentication

Kyrin provides a unified authentication system that combines JWT, Session, RBAC, and OAuth2 into a single Auth class. You can use the high-level Auth facade for convenience, or import individual lower-level functions for finer control.

Installation

Kyrin's auth module is built in — no extra dependencies required.

typescript
import { Auth, createAuth } from "kyrin";

You can also import individual sub-modules directly:

typescript
import { createAccessToken, verifyToken } from "kyrin";
import { createSession, getSession } from "kyrin";
import { defineRole, hasPermission } from "kyrin";

Quick Start

typescript
import { createAuth } from "kyrin";

const auth = createAuth({
  jwtSecret: "your-secret-key-min-32-chars-long",
  sessionTtl: 3600,                // seconds (default: 3600)
  sessionRefreshThreshold: 300,    // seconds (default: 300)
});

// Sign a JWT token pair
const tokens = await auth.signToken("user-123", {
  role: "admin",
});

// Verify a token
const payload = await auth.verifyToken(tokens.accessToken);
console.log(payload?.sub); // "user-123"

// Create a session
const session = auth.createSession("user-123", { role: "admin" });

// Check permissions via RBAC
auth.roles.define("admin", ["*"]);
auth.roles.assign("user-123", ["admin"]);
const canDelete = auth.roles.hasPermission("user-123", "delete:posts");

Auth API Reference

createAuth(options)

Creates a new Auth instance.

typescript
interface AuthOptions {
  jwtSecret: string;              // Required. Secret key for signing JWT tokens.
  sessionTtl?: number;            // Session time-to-live in seconds (default: 3600).
  sessionRefreshThreshold?: number; // Extend session if accessed within this window (default: 300).
}

const auth = createAuth({
  jwtSecret: process.env.JWT_SECRET!,
  sessionTtl: 7200,
  sessionRefreshThreshold: 600,
});

auth.signToken(userId, data?)

Creates an access + refresh token pair for a user.

typescript
const tokens = await auth.signToken("user-42", {
  role: "moderator",
  permissions: ["read:all", "write:posts"],
});

// tokens: { accessToken: string, refreshToken: string, expiresIn: number }
ParameterTypeDescription
userIdstringUnique identifier for the user
dataPartial<TokenPayload>Optional. Extra claims (role, permissions, etc.)

Returns: Promise<TokenPair>

auth.verifyToken(token)

Verifies both access and refresh tokens.

typescript
const payload = await auth.verifyToken(tokens.accessToken);
if (payload) {
  console.log(payload.sub);        // "user-42"
  console.log(payload.role);       // "moderator"
  console.log(payload.permissions); // ["read:all", "write:posts"]
}

Returns: Promise<TokenPayload | null>null if the token is invalid or expired.

auth.verifyAccessToken(token)

Verifies only access tokens. Returns null if the token is a refresh token.

typescript
const payload = await auth.verifyAccessToken(token);

auth.verifyRefreshToken(token)

Verifies only refresh tokens. Returns null if the token is an access token.

typescript
const payload = await auth.verifyRefreshToken(token);

Standalone JWT Functions

For more control, you can use the low-level JWT functions directly.

Configuration

typescript
import { setJWTSecret, getJWTSecret } from "kyrin";

setJWTSecret("my-secret-key");
const secret = getJWTSecret(); // Uint8Array | null

Creating Tokens

typescript
import { createAccessToken, createRefreshToken, createTokenPair } from "kyrin";

const config = {
  secret: "my-secret-key",
  accessExpiry: "15m",    // default: "15m"
  refreshExpiry: "7d",    // default: "7d"
  issuer: "kyrin-app",
  audience: "kyrin-users",
};

// Single token
const accessToken = await createAccessToken(
  { sub: "user-42", role: "admin" },
  config
);

// Token pair
const pair = await createTokenPair(
  { sub: "user-42", role: "admin" },
  config
);
// pair: { accessToken: string, refreshToken: string, expiresIn: number }

Verifying Tokens

typescript
import { verifyToken, verifyAccessToken, verifyRefreshToken, decodeToken } from "kyrin";

const payload = await verifyToken(token, config);
const accessPayload = await verifyAccessToken(token, config);
const refreshPayload = await verifyRefreshToken(token, config);

// decodeToken does NOT verify the signature — only decodes the header + payload
const decoded = decodeToken(token);

TokenPayload

typescript
interface TokenPayload {
  sub: string;             // User ID
  role?: string;           // User role
  permissions?: string[];  // User permissions
  type?: "access" | "refresh";
  exp?: number;            // Expiration timestamp
  iat?: number;            // Issued at timestamp
}

JWTConfig

typescript
interface JWTConfig {
  secret: string;                    // Required. HMAC secret key.
  accessExpiry?: string;             // e.g. "15m", "1h", "7d" (default: "15m")
  refreshExpiry?: string;            // e.g. "7d", "30d" (default: "7d")
  issuer?: string;                   // "iss" claim
  audience?: string;                 // "aud" claim
}

Complete Example

typescript
import { createAuth } from "kyrin";

const auth = createAuth({
  jwtSecret: process.env.JWT_SECRET!,
});

// --- Define roles ---
auth.roles.define("admin", ["*"]);
auth.roles.define("user", ["read:own", "update:own"]);

// --- Login endpoint ---
async function login(userId: string, password: string) {
  // Validate credentials (your logic here)
  const user = await findUser(userId);
  if (!user || user.password !== password) {
    throw new Error("Invalid credentials");
  }

  // Sign tokens
  const tokens = await auth.signToken(userId, { role: user.role });

  // Create a session
  const session = auth.createSession(userId, { role: user.role });

  return { tokens, sessionId: session.id };
}

// --- Protected route middleware ---
async function authMiddleware(c: Context, next: () => Promise<void>) {
  const header = c.req.headers.get("Authorization");
  if (!header?.startsWith("Bearer ")) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  const token = header.slice(7);
  const payload = await auth.verifyAccessToken(token);

  if (!payload) {
    return c.json({ error: "Invalid or expired token" }, 401);
  }

  c.store.userId = payload.sub;
  c.store.role = payload.role;
  await next();
}

// --- Use in a route ---
app.get("/admin/users", authMiddleware, async (c) => {
  if (!auth.roles.hasRole(c.store.userId, "admin")) {
    return c.json({ error: "Forbidden" }, 403);
  }
  const users = await getUsers();
  return c.json(users);
});

Next Steps

Released under the MIT License.