{"_id":"@aginix/adonis-ally-oidc","name":"@aginix/adonis-ally-oidc","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@aginix/adonis-ally-oidc","description":"OpenID Connect (OIDC) driver for AdonisJS Ally — discovery, ID-token verification, PKCE, nonce, and RP-initiated logout. Built for Keycloak and any OIDC provider.","version":"0.1.0","engines":{"node":">=24.0.0"},"type":"module","main":"build/index.js","exports":{".":"./build/index.js","./types":"./build/src/types.js","./errors":"./build/src/errors.js","./discovery":"./build/src/discovery.js","./id_token":"./build/src/id_token.js","./oidc_driver":"./build/src/oidc_driver.js","./define_config":"./build/src/define_config.js"},"scripts":{"copy:templates":"copyfiles \"stubs/**/*.stub\" build","typecheck":"tsc --noEmit","lint":"eslint .","format":"prettier --write .","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","pretest":"npm run lint","test":"c8 npm run quick:test","precompile":"npm run lint","compile":"tsdown && tsc --emitDeclarationOnly --declaration","postcompile":"npm run copy:templates","build":"npm run compile","release":"release-it","version":"npm run build","prepublishOnly":"npm run build"},"keywords":["adonisjs","adonis","ally","oidc","openid-connect","oauth2","keycloak","sso","authentication"],"author":{"name":"n3n"},"license":"MIT","dependencies":{"jose":"^6.2.3"},"devDependencies":{"@adonisjs/ally":"^6.3.0","@adonisjs/assembler":"^8.0.0","@adonisjs/core":"^7.0.1","@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@adonisjs/tsconfig":"^2.0.0","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/oauth-client":"^7.2.0","@poppinss/ts-exec":"^1.4.4","@release-it/conventional-changelog":"^10.0.5","@types/node":"^25.3.5","c8":"^11.0.0","copyfiles":"^2.4.1","eslint":"^10.0.3","prettier":"^3.8.1","release-it":"^19.2.4","tempura":"^0.4.1","tsdown":"^0.21.0","typescript":"^5.9.3"},"peerDependencies":{"@adonisjs/ally":"^6.3.0","@adonisjs/core":"^7.0.0","@adonisjs/assembler":"^8.0.0"},"peerDependenciesMeta":{"@adonisjs/assembler":{"optional":true}},"publishConfig":{"access":"public"},"tsdown":{"entry":["./index.ts","./configure.ts","./stubs/main.ts","./src/types.ts","./src/errors.ts","./src/discovery.ts","./src/id_token.ts","./src/oidc_driver.ts","./src/define_config.ts"],"outDir":"./build","clean":true,"format":"esm","minify":"dce-only","fixedExtension":false,"dts":false,"treeshake":false,"sourcemaps":false,"target":"esnext","external":["@adonisjs/ally","@adonisjs/ally/types","@adonisjs/core","@adonisjs/core/http","@adonisjs/core/types","@adonisjs/core/exceptions","@adonisjs/core/commands/configure","@poppinss/oauth-client","@poppinss/oauth-client/types","jose"]},"release-it":{"git":{"requireCleanWorkingDir":true,"requireUpstream":true,"commitMessage":"chore(adonis-ally-oidc): release ${version}","tagAnnotation":"adonis-ally-oidc-v${version}","push":true,"tagName":"adonis-ally-oidc-v${version}"},"github":{"release":true,"releaseName":"@aginix/adonis-ally-oidc ${version}"},"npm":{"publish":true,"skipChecks":true},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"c8":{"reporter":["text","html"],"exclude":["tests/**"]},"prettier":"@adonisjs/prettier-config","types":"./build/index.d.ts","_id":"@aginix/adonis-ally-oidc@0.1.0","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-LKcfKNwa8Hx3f6PGKqqY1z9pdBFfC3YJiwdhtle1v6sYdF9WEN5EHqHYN/SAR9yxy0UAgZ/VOHNZY9P8UBw+3A==","shasum":"9e0a93943ac9b5dacf6f023a59651ac9d206e774","tarball":"https://registry.npmjs.org/@aginix/adonis-ally-oidc/-/adonis-ally-oidc-0.1.0.tgz","fileCount":20,"unpackedSize":44930,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDoGw05Gys+seOy1qUjUvFzGJuBGbyOOQReOVBVU8ci8QIgHADRBZ7WedwIU9ZnHAB6Yw91MvMVu4UIbWkh3htx1bM="}]},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"directories":{},"maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/adonis-ally-oidc_0.1.0_1781848993290_0.9323027333963168"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-19T06:03:13.126Z","0.1.0":"2026-06-19T06:03:13.424Z","modified":"2026-06-19T06:03:13.663Z"},"maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"description":"OpenID Connect (OIDC) driver for AdonisJS Ally — discovery, ID-token verification, PKCE, nonce, and RP-initiated logout. Built for Keycloak and any OIDC provider.","keywords":["adonisjs","adonis","ally","oidc","openid-connect","oauth2","keycloak","sso","authentication"],"author":{"name":"n3n"},"license":"MIT","readme":"# @aginix/adonis-ally-oidc\n\n> Generic **OpenID Connect** driver for [AdonisJS Ally](https://docs.adonisjs.com/guides/authentication/social-authentication) — discovery, ID-token verification, PKCE, nonce, and RP-initiated logout. Built for Keycloak and any OIDC-compliant provider.\n\n[![npm version](https://img.shields.io/npm/v/@aginix/adonis-ally-oidc.svg)](https://www.npmjs.com/package/@aginix/adonis-ally-oidc)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Node >= 24](https://img.shields.io/badge/node-%3E%3D24-brightgreen.svg)](https://nodejs.org)\n\n`@adonisjs/ally` ships drivers for Google, GitHub, Discord, and friends, but no generic OpenID Connect driver. This package fills that gap: give it an `issuer` and it discovers the endpoints, verifies the `id_token` (signature + claims + nonce), runs PKCE, and exposes RP-initiated logout — all behind ally's familiar `ally.use('...')` API. Works with Keycloak (first-class), Auth0, Okta, Microsoft Entra ID, Zitadel, Google, and any spec-compliant provider.\n\n## Features\n\nA bare OAuth2 driver can call a userinfo endpoint, but it never proves _who_ issued the token. This driver does the OIDC-specific work:\n\n| Feature | What it does |\n|---|---|\n| **Discovery** | `issuer` → `.well-known/openid-configuration` → endpoints + JWKS, resolved once at boot |\n| **ID-token verification** | RS256/ES256/… signature via remote JWKS, plus `iss`, `aud`/`azp`, `exp`/`iat`/`nbf` (clock skew), and `nonce` — strict by default |\n| **PKCE** | S256, on by default |\n| **Nonce** | issued in an encrypted cookie, verified against the id_token (replay protection) |\n| **RP-Initiated Logout** | redirect to the provider's `end_session_endpoint` with `id_token_hint` + `post_logout_redirect_uri` |\n| **Typed** | `ally.use('keycloak')` is fully typed, including the returned user and tokens |\n\n## Requirements\n\n- Node.js >= 24\n- `@adonisjs/ally` ^6.3.0 and `@adonisjs/core` ^7 (peer dependencies)\n\n## Installation\n\n```sh\nnpm i @aginix/adonis-ally-oidc\nnode ace configure @aginix/adonis-ally-oidc\n```\n\nInstall and configure `@adonisjs/ally` first, if you haven't:\n\n```sh\nnode ace add @adonisjs/ally\n```\n\n`node ace configure` adds env validations and prints the config snippet. Set your secrets:\n\n```dotenv\n# .env\nOIDC_ISSUER=https://sso.example.com/realms/myrealm\nOIDC_CLIENT_ID=my-app\nOIDC_CLIENT_SECRET=super-secret\n```\n\n## Configure the provider\n\nRegister an `oidc(...)` provider in `config/ally.ts`:\n\n```ts\nimport env from '#start/env'\nimport { defineConfig } from '@adonisjs/ally'\nimport { oidc } from '@aginix/adonis-ally-oidc'\n\nexport default defineConfig({\n  keycloak: oidc({\n    issuer: env.get('OIDC_ISSUER'),\n    clientId: env.get('OIDC_CLIENT_ID'),\n    clientSecret: env.get('OIDC_CLIENT_SECRET'),\n    callbackUrl: 'http://localhost:3333/auth/keycloak/callback',\n    postLogoutRedirectUri: 'http://localhost:3333',\n  }),\n})\n```\n\nDiscovery and the JWKS are resolved once at boot, so a wrong or unreachable `issuer` fails startup with a clear error.\n\n## Usage\n\n```ts\nimport router from '@adonisjs/core/services/router'\n\n// 1) Send the user to the provider\nrouter.get('/auth/keycloak/redirect', ({ ally }) => {\n  return ally.use('keycloak').redirect()\n})\n\n// 2) Handle the callback\nrouter.get('/auth/keycloak/callback', async ({ ally, auth, response, session }) => {\n  const kc = ally.use('keycloak')\n\n  if (kc.accessDenied()) return 'You cancelled the login'\n  if (kc.stateMisMatch()) return 'Request expired, retry'\n  if (kc.hasError()) return kc.getError()\n\n  const oidcUser = await kc.user()\n  // oidcUser.id            -> the \"sub\" claim\n  // oidcUser.email, .name, .nickName (preferred_username)\n  // oidcUser.original      -> full verified claims (e.g. Keycloak realm_access.roles)\n  // oidcUser.token.idToken -> raw id_token, store it for logout\n\n  // `externalId` is your own column; linking by the stable `sub` survives\n  // email changes. (The demo app links by email — either works.)\n  const user = await User.firstOrCreate(\n    { externalId: oidcUser.id },\n    { email: oidcUser.email!, fullName: oidcUser.name }\n  )\n  await auth.use('web').login(user)\n\n  session.put('id_token', oidcUser.token.idToken)\n  return response.redirect('/dashboard')\n})\n\n// 3) Log out locally and at the provider (RP-initiated logout)\nrouter.get('/auth/keycloak/logout', async ({ ally, auth, session }) => {\n  const idTokenHint = session.get('id_token')\n  await auth.use('web').logout()\n  session.forget('id_token')\n\n  return ally.use('keycloak').logout({ idTokenHint })\n})\n```\n\n`logout(...)` redirects to the provider's `end_session_endpoint`. Register your `postLogoutRedirectUri` with the provider (Keycloak: client → _Valid post logout redirect URIs_). Keycloak v18+ requires the `id_token_hint` (or a registered post-logout URI + `client_id`, which this driver always sends).\n\n### How id-token verification works\n\nOn the callback, `user()` exchanges the code at the token endpoint and then verifies the returned `id_token` with [`jose`](https://github.com/panva/jose): the signature against the provider's JWKS (asymmetric algorithms only — `none` and symmetric `HS*` are rejected), plus `iss`, `aud`/`azp`, `exp`/`iat`/`nbf` (with optional clock skew), and the `nonce` issued during the redirect. Identity is taken from these verified claims, never from an unverified source.\n\n### Example app\n\nSee [`apps/demo`](https://github.com/Aginix/adonis-vulcan/tree/main/apps/demo) in the repo for a complete Inertia/React integration: a \"Login with Keycloak\" button, the callback wiring, and RP-initiated logout.\n\n## Keycloak notes\n\n- `issuer` is `https://{host}/realms/{realm}` (Keycloak ≥ 17 — older builds use `/auth/realms/{realm}`).\n- Roles live in `realm_access.roles` / `resource_access.{client}.roles` on the verified claims — read them from `oidcUser.original`.\n- Map extra claims (picture, groups) with Keycloak _client scopes / protocol mappers_ so they land in the id_token.\n\n## Configuration reference\n\n| Option | Default | Description |\n|---|---|---|\n| `clientId`, `clientSecret`, `callbackUrl` | — | Standard OAuth2 client credentials |\n| `issuer` | — | Issuer URL for discovery |\n| `scopes` | `['openid','profile','email']` | Requested scopes |\n| `usePKCE` | `true` | PKCE (S256) |\n| `postLogoutRedirectUri` | — | Default redirect after logout |\n| `fetchUserInfo` | `false` | Also call userinfo and merge (its `sub` must match the id_token) |\n| `clockTolerance` | `0` | Skew tolerance for id_token time claims (seconds or `'30s'`) |\n| `cookieName` | `'oidc'` | Cookie-name prefix (set per provider if you register several) |\n| `extraAuthParams` | — | Extra static auth-request params, e.g. `{ prompt: 'login' }` |\n\n### Escape hatches\n\n- **Skip discovery** — pass `authorizeUrl` + `accessTokenUrl` + `jwksUri` (+ optional `userInfoUrl`, `endSessionEndpoint`) explicitly.\n- **Restrict / pin algorithms** — `idTokenSigningAlgs: ['RS256']`.\n- **Disable verification** — `verifyIdToken: false` decodes the id_token without checking its signature. Only do this behind a fully trusted channel; it removes the package's core security guarantee.\n\n## Contributing\n\nThis package lives in the [adonis-vulcan monorepo](https://github.com/Aginix/adonis-vulcan) (pnpm, Node >= 24). Issues and PRs welcome.\n\n```sh\npnpm install\npnpm --filter @aginix/adonis-ally-oidc test     # lint + japa + coverage\npnpm --filter @aginix/adonis-ally-oidc build\n```\n\n## License\n\nMIT © [Aginix](https://github.com/Aginix). See [LICENSE.md](https://github.com/Aginix/adonis-vulcan/blob/main/LICENSE.md).\n","readmeFilename":"README.md","_rev":"1-4abf64e7e786001d09dce1820ae73326"}