studiokare-auth

A single-tenant passwordless magic-link auth library for Deno + Hono + Val Town SQLite, with optional multi-client OAuth2 authorization code flow.

Quick Start

import { createAuthApp } from "./backend/index.ts"; const { authApp, authMiddleware, runAuthMigrations } = createAuthApp({ appName: "MyApp", }); await runAuthMigrations(); const app = new Hono(); app.use("*", authMiddleware); app.route("/auth", authApp);

With OAuth2

const { authApp, oauthApp, authMiddleware, runAuthMigrations } = createAuthApp({ appName: "MyApp", oauth: { clients: [ { clientId: "my-client", clientSecret: "a-strong-secret", redirectUris: ["https://my-client-app.com/callback"], name: "My Client App", }, ], jwtSecret: "a-32-byte-or-longer-secret-key!!", // HMAC-SHA256 }, }); await runAuthMigrations(); const app = new Hono(); app.use("*", authMiddleware); app.route("/auth", authApp); if (oauthApp) app.route("/oauth", oauthApp);

Auth Endpoints (/auth)

MethodPathDescription
POST/magic-linkRequest a magic link. Body: { "email": "..." }
GET/magic-link/:tokenVerify magic link and create session
POST/logoutDestroy current session
GET/meReturn current user or null
POST/change-emailRequest email change. Body: { "email": "..." }
GET/change-email/:tokenConfirm email change

OAuth2 Endpoints (/oauth)

Only available when oauth is configured. Implements the OAuth2 authorization code flow with stateless JWT access tokens.

MethodPathDescription
GET/authorizeStart authorization flow. Query: response_type=code&client_id=...&redirect_uri=...&state=...
POST/authorizeUser consents; generates auth code and redirects to redirect_uri
POST/tokenExchange auth code for JWT. Form: grant_type=authorization_code&code=...&redirect_uri=...&client_id=...&client_secret=...
GET/userinfoGet user info from JWT. Header: Authorization: Bearer {jwt}
POST/usersRegister a user (client-authenticated). Header: Authorization: Basic base64(client_id:client_secret). Body: { "email": "..." }

OAuth2 Flow

  1. Client redirects user to /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...
  2. User authenticates via magic link (if not already logged in)
  3. User sees consent page and authorizes
  4. Auth server redirects to redirect_uri?code=...&state=...
  5. Client exchanges code for JWT at POST /oauth/token
  6. Client uses JWT at GET /oauth/userinfo

Token Response

{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }

Userinfo Response

{ "sub": "user-id", "email": "user@example.com" }

Example: Client App

See examples/client-app.ts for a complete example of two Val Town HTTP vals working together — an auth server (identity provider) and a client app that uses the OAuth2 flow for passwordless login.

Configuration

interface AuthConfig { appName: string; tablePrefix?: string; // default: "studiokare" cookieName?: string; // default: "studiokare_session" sessionLifetimeMs?: number; // default: 30 days magicLinkTtlMs?: number; // default: 15 minutes emailService?: EmailService; emailTemplates?: { ... }; oauth?: { clients: OAuthClient[]; jwtSecret: string; // HMAC-SHA256 key (>= 32 bytes) accessTokenTtlMs?: number; // default: 1 hour authCodeTtlMs?: number; // default: 10 minutes }; }

Environment Variables

Scaleway Email Service

Used by ScalewayEmailService in backend/email.ts to send transactional emails (magic links, invites, email change confirmations).

VariableRequiredDescription
SCW_REGIONYesScaleway region (used to build the API endpoint)
SCW_API_URLYesScaleway API base URL
SCW_SECRET_KEYYesScaleway authentication secret
SCW_PROJECT_IDYesScaleway project identifier
SCW_FROM_EMAILYesSender email address for transactional emails
SCW_FROM_NAMENoSender display name (optional)

If SCW_SECRET_KEY and SCW_PROJECT_ID are not set, the system falls back to ConsoleEmailService, which logs emails to the console instead of sending them.