Skip to content

Commit ea4081b

Browse files
committed
Add ThunderID provider helper for Better Auth's Generic OAuth plugin
Introduces @thunderid/better-auth, a community provider helper that returns a typed GenericOAuthConfig for a ThunderID issuer to use with Better Auth's generic-oauth plugin. All OAuth 2.0 / OIDC handling is performed by Better Auth itself, so there is no ThunderID SDK dependency or protocol logic to keep in sync. The callback route registered with ThunderID is /callback/:providerId, not /oauth2/callback/:providerId, matching the Generic OAuth plugin's core callback route (verified against a running sample app sign-in). Related: thunder-id/thunderid#3559 Signed-off-by: Brion <info@brionmario.com>
1 parent b73a371 commit ea4081b

17 files changed

Lines changed: 947 additions & 28 deletions

‎packages/better-auth/.editorconfig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../.editorconfig

‎packages/better-auth/.gitignore‎

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# Snowpack dependency directory (https://snowpack.dev/)
45+
web_modules/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Optional stylelint cache
57+
.stylelintcache
58+
59+
# Optional REPL history
60+
.node_repl_history
61+
62+
# Output of 'npm pack'
63+
*.tgz
64+
65+
# Yarn Integrity file
66+
.yarn-integrity
67+
68+
# dotenv environment variable files
69+
.env
70+
.env.*
71+
!.env.example
72+
73+
# parcel-bundler cache (https://parceljs.org/)
74+
.cache
75+
.parcel-cache
76+
77+
# Next.js build output
78+
.next
79+
out
80+
81+
# Nuxt.js build / generate output
82+
.nuxt
83+
dist
84+
.output
85+
86+
# Gatsby files
87+
.cache/
88+
# Comment in the public line in if your project uses Gatsby and not Next.js
89+
# https://nextjs.org/blog/next-9-1#public-directory-support
90+
# public
91+
92+
# vuepress build output
93+
.vuepress/dist
94+
95+
# vuepress v2.x temp and cache directory
96+
.temp
97+
.cache
98+
99+
# Sveltekit cache directory
100+
.svelte-kit/
101+
102+
# vitepress build output
103+
**/.vitepress/dist
104+
105+
# vitepress cache directory
106+
**/.vitepress/cache
107+
108+
# Docusaurus cache and generated files
109+
.docusaurus
110+
111+
# Serverless directories
112+
.serverless/
113+
114+
# FuseBox cache
115+
.fusebox/
116+
117+
# DynamoDB Local files
118+
.dynamodb/
119+
120+
# Firebase cache directory
121+
.firebase/
122+
123+
# TernJS port file
124+
.tern-port
125+
126+
# Stores VSCode versions used for testing VSCode extensions
127+
.vscode-test
128+
129+
# yarn v3
130+
.pnp.*
131+
.yarn/*
132+
!.yarn/patches
133+
!.yarn/plugins
134+
!.yarn/releases
135+
!.yarn/sdks
136+
!.yarn/versions
137+
138+
# Vite files
139+
vite.config.js.timestamp-*
140+
vite.config.ts.timestamp-*
141+
.vite/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/dist
2+
/build
3+
/node_modules
4+
/coverage

‎packages/better-auth/README.md‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
![ThunderID Browser SDK](https://raw.githubusercontent.com/thunder-id/thunderid/refs/heads/main/docs/static/assets/images/readme/repo-banner-better-auth.png)
2+
3+
ThunderID provider helper for the [Better Auth](https://better-auth.com)
4+
[Generic OAuth plugin](https://better-auth.com/docs/plugins/generic-oauth).
5+
6+
This is a
7+
[community provider helper](https://better-auth.com/docs/authentication/other-social-providers#community-provider-helpers):
8+
it returns a typed `GenericOAuthConfig` for a ThunderID issuer. All OAuth 2.0 / OIDC handling is performed by Better
9+
Auth itself — this package only supplies configuration, so there is no ThunderID SDK dependency and no protocol logic to
10+
keep in sync.
11+
12+
## Pre-requisites
13+
14+
- Requires `better-auth` >= 1.7.0 as a peer dependency.
15+
- A running ThunderID instance with an OAuth 2.0 / OIDC application registered. See the [ThunderID documentation](https://thunderid.dev/) for details.
16+
17+
## Installation
18+
19+
```bash
20+
npm install @thunderid/better-auth
21+
```
22+
23+
## Usage
24+
25+
```ts
26+
import {thunderid} from '@thunderid/better-auth';
27+
import {betterAuth} from 'better-auth';
28+
import {genericOAuth} from 'better-auth/plugins';
29+
30+
export const auth = betterAuth({
31+
plugins: [
32+
genericOAuth({
33+
config: [
34+
thunderid({
35+
clientId: process.env.THUNDERID_CLIENT_ID!,
36+
clientSecret: process.env.THUNDERID_CLIENT_SECRET!,
37+
issuer: process.env.THUNDERID_ISSUER!,
38+
}),
39+
],
40+
}),
41+
],
42+
});
43+
```
44+
45+
Use your existing Better Auth client, or create one. No client plugin is required, since the generic OAuth
46+
plugin exposes sign-in through the standard social-provider API:
47+
48+
```ts
49+
import {createAuthClient} from 'better-auth/react';
50+
51+
export const authClient = createAuthClient();
52+
```
53+
54+
Sign in from the client with the `thunderid` provider ID:
55+
56+
```ts
57+
await authClient.signIn.social({
58+
provider: 'thunderid',
59+
callbackURL: '/dashboard',
60+
});
61+
```
62+
63+
## Callback URL
64+
65+
Register this redirect URI on your ThunderID application:
66+
67+
```
68+
{baseURL}/api/auth/callback/thunderid
69+
```
70+
71+
For example, `http://localhost:3000/api/auth/callback/thunderid` in development.
72+
73+
## Options
74+
75+
| Option | Type | Default | Description |
76+
| ----------------------- | ---------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77+
| `issuer` | `string` | — | **Required.** ThunderID issuer URL, e.g. `https://thunderid.example.com`. A trailing slash is trimmed. The OIDC discovery URL is derived as `{issuer}/.well-known/openid-configuration`, so all endpoints come from the discovery document. |
78+
| `clientId` | `string` | — | **Required.** OAuth client ID. |
79+
| `clientSecret` | `string` | — | OAuth client secret. Omit for public clients using `tokenEndpointAuth: {method: 'none'}`. |
80+
| `scopes` | `string[]` | `['openid', 'profile', 'email']` | Requested scopes. |
81+
| `tokenEndpointAuth` | `object` | provider default | Token endpoint authentication method, e.g. `{method: 'client_secret_post'}`. |
82+
| `pkce` | `boolean` | discovery default | Force PKCE on or off. |
83+
| `redirectURI` | `string` | Better Auth default | Override the callback URL. |
84+
| `endSessionEndpoint` | `string` | discovery default | RP-initiated logout endpoint. |
85+
| `postLogoutRedirectURI` | `string` | — | Where ThunderID returns the user after logout. |
86+
| `disableProviderLogout` | `boolean` | `false` | Skip provider logout on sign-out. |
87+
| `disableImplicitSignUp` | `boolean` | `false` | Require an explicit sign-up request before creating a user. |
88+
| `disableSignUp` | `boolean` | `false` | Reject sign-in for users who do not already exist. |
89+
| `overrideUserInfo` | `boolean` | `false` | Refresh the stored user profile from ThunderID on every sign-in. |
90+
91+
Every option other than `issuer` is passed through from Better Auth's
92+
[`BaseOAuthProviderOptions`](https://better-auth.com/docs/authentication/other-social-providers#built-in-provider-helpers)
93+
and behaves exactly as it does for the built-in provider helpers.
94+
95+
## License
96+
97+
This project is licensed under the [Apache License 2.0](https://github.com/thunder-id/thunderid/blob/main/LICENSE).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2026 The ThunderID Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import thunderIdPlugin from '@thunderid/eslint-plugin';
5+
6+
export default [
7+
{
8+
ignores: ['dist/**', 'build/**', 'node_modules/**', 'coverage/**'],
9+
},
10+
...thunderIdPlugin.configs.typescript,
11+
...thunderIdPlugin.configs.vitest,
12+
];

‎packages/better-auth/package.json‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "@thunderid/better-auth",
3+
"version": "0.0.0",
4+
"description": "ThunderID for Better Auth",
5+
"keywords": [
6+
"thunderid",
7+
"better-auth",
8+
"oauth",
9+
"oidc",
10+
"generic-oauth",
11+
"helper"
12+
],
13+
"homepage": "https://github.com/thunder-id/javascript-sdks/tree/main/packages/better-auth#readme",
14+
"bugs": {
15+
"url": "https://github.com/thunder-id/thunderid/issues"
16+
},
17+
"author": "The ThunderID Authors",
18+
"license": "Apache-2.0",
19+
"type": "module",
20+
"main": "dist/index.js",
21+
"module": "dist/index.js",
22+
"commonjs": "dist/cjs/index.cjs",
23+
"exports": {
24+
"import": "./dist/index.js",
25+
"require": "./dist/cjs/index.cjs"
26+
},
27+
"files": [
28+
"dist",
29+
"README.md",
30+
"LICENSE"
31+
],
32+
"types": "dist/index.d.ts",
33+
"repository": {
34+
"type": "git",
35+
"url": "https://github.com/thunder-id/javascript-sdks",
36+
"directory": "packages/better-auth"
37+
},
38+
"scripts": {
39+
"build": "pnpm clean:dist && rolldown -c rolldown.config.js && tsc -p tsconfig.lib.json --emitDeclarationOnly --outDir dist",
40+
"clean": "pnpm clean:node_modules && pnpm clean:dist",
41+
"clean:dist": "rimraf dist",
42+
"clean:node_modules": "rimraf node_modules",
43+
"format:check": "prettier --check --cache .",
44+
"format:fix": "prettier --write --cache .",
45+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.cjs,.mjs",
46+
"lint:fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx,.cjs,.mjs",
47+
"test": "vitest",
48+
"typecheck": "tsc -p tsconfig.lib.json"
49+
},
50+
"devDependencies": {
51+
"@thunderid/eslint-plugin": "catalog:",
52+
"@thunderid/prettier-config": "catalog:",
53+
"@types/node": "catalog:",
54+
"better-auth": "^1.7.2",
55+
"eslint": "catalog:",
56+
"prettier": "catalog:",
57+
"rimraf": "catalog:",
58+
"rolldown": "catalog:",
59+
"typescript": "catalog:",
60+
"vitest": "catalog:"
61+
},
62+
"peerDependencies": {
63+
"better-auth": ">=1.7.0"
64+
},
65+
"publishConfig": {
66+
"access": "public"
67+
}
68+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2026 The ThunderID Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import config from '@thunderid/prettier-config';
5+
6+
export default config;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2026 The ThunderID Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import {readFileSync} from 'fs';
5+
import {join} from 'path';
6+
import {defineConfig} from 'rolldown';
7+
8+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
9+
10+
const external = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), /^better-auth\//];
11+
12+
const commonOptions = {
13+
input: [join('src', 'index.ts')],
14+
preserveModules: true,
15+
external,
16+
platform: 'neutral',
17+
target: 'es2020',
18+
sourcemap: true,
19+
};
20+
21+
export default defineConfig([
22+
// ESM build
23+
{
24+
...commonOptions,
25+
output: {
26+
dir: 'dist',
27+
format: 'esm',
28+
preserveModulesRoot: 'src',
29+
},
30+
},
31+
// CommonJS build
32+
{
33+
...commonOptions,
34+
output: {
35+
dir: join('dist', 'cjs'),
36+
entryFileNames: '[name].cjs',
37+
format: 'cjs',
38+
preserveModulesRoot: 'src',
39+
},
40+
},
41+
]);

0 commit comments

Comments
 (0)