Skip to main content
willy.im / idp

One identity.
All your apps.

An open-source OAuth 2.1 and OpenID Connect provider on Cloudflare Workers. Your apps add “Sign in with willy.im” and delete their account code.

Free and open sourceCloudflare Workers + D1MPL-2.0
{
  "iss": "https://idp.willy.im/auth",
  "sub": "usr_8f2c1a",
  "email": "ada@example.com",
  "picture": "https://idp.willy.im/avatar/usr_8f2c1a",
  "https://willy.im/workspaces": [
    { "id": "ws_acme", "role": "admin" }
  ],
  "permissions": [
    "notes:read",
    "notes:write",
    "kirby:thread:t_14f451b6"
  ]
}

The ID token Notes gets back. Example values.

Everything an app would otherwise build

Authentication is central. Authorization stays with each app, fed by claims the IdP resolves.

  • Sign-in

    Magic links, one-time codes and passkeys

    No passwords to store. Discord and other identities can be linked to the same account.
  • One identity

    One user store, many apps

    A person has a single identity across every app. Each app decides who gets in and what they can do.
  • Workspaces

    Per-app workspaces and roles in the token

    The token lists every workspace the user has in the requesting app, and never one from another app.
  • Permissions

    A permission catalog per app

    Apps declare their permissions. The IdP resolves each user's grants, down to a single resource like one thread, and emits them as a claim.
  • API keys

    Scoped keys for machines

    Keys carry the same permissions model as tokens, so apps can drop their own key systems.
  • Management API

    HTTP, OpenAPI and MCP, built on kit

    Every console action is one method, so an agent can provision apps, users and workspaces with a key.
  • Console

    Admin console for apps, users and members

    Invitations, secret rotation, impersonation and an audit log of every change.
  • Custom domains

    Your app's own sign-in domain

    Serve the IdP from idp.yourapp.com so its session cookie stays first-party.

Add it to an app

@willyim/idp is the client: OIDC, server sessions and React Router guards. Its only runtime dependency is zod, and it runs on Workers, Node and Bun.

import { createIdp } from "@willyim/idp"
import { drizzleSessions } from "@willyim/idp/drizzle"

export const idp = createIdp({
  issuer: "https://idp.willy.im/auth",
  clientId: env.IDP_CLIENT_ID,
  clientSecret: env.IDP_CLIENT_SECRET,
  sessions: drizzleSessions(db, schema.idpSession),
  session: { secret: env.SESSION_SECRET, expiresIn: "30d" },
})
$npm install @willyim/idp

Run your own

No plans, no seats, no tiers. Fork it and it's yours. The whole IdP is one Worker and one D1 database.

  1. Fork the repository

    The IdP lives in apps/idp.
  2. Create a D1 database

    Point wrangler.jsonc at it, then apply the migrations.
    wrangler d1 create idp
    npm run db:migrate-prod --workspace=idp
  3. Deploy

    npm run deploy --workspace=idp

Roadmap

It already runs sign-in for the apps on willy.im.

Shipped

  • Email and passkey sign-in
  • OIDC provider and discovery
  • Workspaces and role claims
  • Permission catalogs and resource grants
  • API keys and management keys
  • Admin console, audit, impersonation
  • Custom domains

In progress

  • Per-app user metadata

Later

  • Profile editing in one place
  • MFA
  • Organizations
  • SAML and SCIM