@authu/node 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/jwks.d.ts +18 -0
- package/dist/jwks.d.ts.map +1 -0
- package/dist/jwks.js +37 -0
- package/dist/jwks.js.map +1 -0
- package/dist/middleware.d.ts +22 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +41 -0
- package/dist/middleware.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verifyToken.d.ts +10 -0
- package/dist/verifyToken.d.ts.map +1 -0
- package/dist/verifyToken.js +44 -0
- package/dist/verifyToken.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# @authu/node
|
|
2
|
+
|
|
3
|
+
Node.js SDK for AuthU - Centralized Multi-Tenant Authentication Service.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @authu/node
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @authu/node
|
|
11
|
+
# or
|
|
12
|
+
yarn add @authu/node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### 1. Verify JWT Tokens
|
|
18
|
+
|
|
19
|
+
Use `verifyToken` to validate and decode JWT tokens:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {verifyToken} from '@authu/node';
|
|
23
|
+
|
|
24
|
+
const result = await verifyToken(token, {
|
|
25
|
+
domain: 'auth.example.com',
|
|
26
|
+
audience: 'https://api.example.com'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(result.payload.sub); // User ID
|
|
30
|
+
console.log(result.payload.email); // User email
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Fastify Middleware
|
|
34
|
+
|
|
35
|
+
Use `createAuthUMiddleware` to protect your Fastify routes:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import Fastify from 'fastify';
|
|
39
|
+
import {createAuthUMiddleware} from '@authu/node';
|
|
40
|
+
|
|
41
|
+
const fastify = Fastify();
|
|
42
|
+
|
|
43
|
+
// Register the middleware
|
|
44
|
+
fastify.register(
|
|
45
|
+
createAuthUMiddleware({
|
|
46
|
+
domain: 'auth.example.com',
|
|
47
|
+
audience: 'https://api.example.com'
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Protected route
|
|
52
|
+
fastify.get(
|
|
53
|
+
'/api/profile',
|
|
54
|
+
{preHandler: [fastify.verifyAuthU]},
|
|
55
|
+
async request => {
|
|
56
|
+
// Access the authenticated user
|
|
57
|
+
return {user: request.authUUser};
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Optional Authentication
|
|
63
|
+
|
|
64
|
+
For routes where authentication is optional:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
fastify.register(
|
|
68
|
+
createAuthUMiddleware({
|
|
69
|
+
domain: 'auth.example.com',
|
|
70
|
+
optional: true
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
fastify.get('/api/public', {preHandler: [fastify.verifyAuthU]}, async request => {
|
|
75
|
+
if (request.authUUser) {
|
|
76
|
+
return {message: `Hello ${request.authUUser.name}`};
|
|
77
|
+
}
|
|
78
|
+
return {message: 'Hello guest'};
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Custom JWKS Client
|
|
83
|
+
|
|
84
|
+
For advanced use cases, you can provide your own JWKS client:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import {JwksClient, verifyToken} from '@authu/node';
|
|
88
|
+
|
|
89
|
+
const jwksClient = new JwksClient({
|
|
90
|
+
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
|
|
91
|
+
cacheMaxAge: 300000 // 5 minutes cache
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = await verifyToken(token, {
|
|
95
|
+
domain: 'auth.example.com',
|
|
96
|
+
jwksClient
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
### verifyToken(token, options)
|
|
103
|
+
|
|
104
|
+
Verifies and decodes a JWT token.
|
|
105
|
+
|
|
106
|
+
**Options:**
|
|
107
|
+
|
|
108
|
+
| Option | Type | Required | Description |
|
|
109
|
+
|--------|------|----------|-------------|
|
|
110
|
+
| `domain` | `string` | Yes | AuthU server domain (without https://) |
|
|
111
|
+
| `audience` | `string` | No | Expected audience claim |
|
|
112
|
+
| `issuer` | `string` | No | Expected issuer (default: `https://{domain}`) |
|
|
113
|
+
| `jwksClient` | `JwksClient` | No | Custom JWKS client instance |
|
|
114
|
+
|
|
115
|
+
**Returns:** `Promise<VerifiedToken>`
|
|
116
|
+
|
|
117
|
+
### createAuthUMiddleware(options)
|
|
118
|
+
|
|
119
|
+
Creates a Fastify plugin for JWT authentication.
|
|
120
|
+
|
|
121
|
+
**Options:**
|
|
122
|
+
|
|
123
|
+
| Option | Type | Required | Description |
|
|
124
|
+
|--------|------|----------|-------------|
|
|
125
|
+
| `domain` | `string` | Yes | AuthU server domain |
|
|
126
|
+
| `audience` | `string` | No | Expected audience claim |
|
|
127
|
+
| `issuer` | `string` | No | Expected issuer |
|
|
128
|
+
| `optional` | `boolean` | No | If true, don't error on missing/invalid tokens |
|
|
129
|
+
|
|
130
|
+
**Decorators added:**
|
|
131
|
+
|
|
132
|
+
- `fastify.verifyAuthU` - Prehandler function for route protection
|
|
133
|
+
- `request.authUUser` - Authenticated user data (or null if optional)
|
|
134
|
+
|
|
135
|
+
### JwksClient
|
|
136
|
+
|
|
137
|
+
JWKS client with automatic caching.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const client = new JwksClient({
|
|
141
|
+
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
|
|
142
|
+
cacheMaxAge: 600000 // 10 minutes (default)
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Get a key by kid
|
|
146
|
+
const key = await client.getKey('key-id');
|
|
147
|
+
|
|
148
|
+
// Get all keys
|
|
149
|
+
const jwks = await client.getJwks();
|
|
150
|
+
|
|
151
|
+
// Clear cache
|
|
152
|
+
client.clearCache();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Types
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface AuthUUser {
|
|
159
|
+
sub: string;
|
|
160
|
+
email?: string;
|
|
161
|
+
emailVerified?: boolean;
|
|
162
|
+
name?: string;
|
|
163
|
+
picture?: string;
|
|
164
|
+
scope?: string;
|
|
165
|
+
clientId?: string;
|
|
166
|
+
tenantId?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface VerifiedToken {
|
|
170
|
+
payload: AuthUUser;
|
|
171
|
+
header: {
|
|
172
|
+
alg: string;
|
|
173
|
+
typ?: string;
|
|
174
|
+
kid?: string;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
### Build
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
pnpm run build
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Lint
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
pnpm run lint
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Publishing
|
|
194
|
+
|
|
195
|
+
### Prerequisites
|
|
196
|
+
|
|
197
|
+
- Be logged in to npm: `npm login`
|
|
198
|
+
- Have publish rights on `@authu` scope
|
|
199
|
+
|
|
200
|
+
### Publish a New Version
|
|
201
|
+
|
|
202
|
+
1. Update version in `package.json`
|
|
203
|
+
2. Build and publish:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
pnpm run build
|
|
207
|
+
pnpm publish --access public
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The `--access public` flag is required for scoped packages.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { verifyToken, type VerifyTokenOptions } from './verifyToken.js';
|
|
2
|
+
export { createAuthUMiddleware, type AuthUMiddlewareOptions } from './middleware.js';
|
|
3
|
+
export { JwksClient } from './jwks.js';
|
|
4
|
+
export type { AuthUUser, VerifiedToken, JWK, JWKS } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,EAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AACrC,YAAY,EAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,EAAC,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAA0B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC"}
|
package/dist/jwks.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import type { JWKS } from './types.js';
|
|
3
|
+
interface JwksClientOptions {
|
|
4
|
+
jwksUri: string;
|
|
5
|
+
cacheMaxAge?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class JwksClient {
|
|
8
|
+
private jwksUri;
|
|
9
|
+
private cacheMaxAge;
|
|
10
|
+
private cachedJwks;
|
|
11
|
+
private cacheExpiry;
|
|
12
|
+
constructor(options: JwksClientOptions);
|
|
13
|
+
getKey(kid: string): Promise<Awaited<ReturnType<typeof jose.importJWK>>>;
|
|
14
|
+
getJwks(): Promise<JWKS>;
|
|
15
|
+
clearCache(): void;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=jwks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAErC,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,WAAW,CAAa;gBAEpB,OAAO,EAAE,iBAAiB;IAKhC,MAAM,CACV,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAWhD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB9B,UAAU,IAAI,IAAI;CAInB"}
|
package/dist/jwks.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
export class JwksClient {
|
|
3
|
+
jwksUri;
|
|
4
|
+
cacheMaxAge;
|
|
5
|
+
cachedJwks = null;
|
|
6
|
+
cacheExpiry = 0;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.jwksUri = options.jwksUri;
|
|
9
|
+
this.cacheMaxAge = options.cacheMaxAge ?? 600000; // 10 minutes default
|
|
10
|
+
}
|
|
11
|
+
async getKey(kid) {
|
|
12
|
+
const jwks = await this.getJwks();
|
|
13
|
+
const key = jwks.keys.find(k => k.kid === kid);
|
|
14
|
+
if (!key) {
|
|
15
|
+
throw new Error(`Key with kid "${kid}" not found in JWKS`);
|
|
16
|
+
}
|
|
17
|
+
return jose.importJWK(key);
|
|
18
|
+
}
|
|
19
|
+
async getJwks() {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (this.cachedJwks && now < this.cacheExpiry) {
|
|
22
|
+
return this.cachedJwks;
|
|
23
|
+
}
|
|
24
|
+
const response = await fetch(this.jwksUri);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch JWKS: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
this.cachedJwks = (await response.json());
|
|
29
|
+
this.cacheExpiry = now + this.cacheMaxAge;
|
|
30
|
+
return this.cachedJwks;
|
|
31
|
+
}
|
|
32
|
+
clearCache() {
|
|
33
|
+
this.cachedJwks = null;
|
|
34
|
+
this.cacheExpiry = 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=jwks.js.map
|
package/dist/jwks.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAQ7B,MAAM,OAAO,UAAU;IACb,OAAO,CAAS;IAChB,WAAW,CAAS;IACpB,UAAU,GAAgB,IAAI,CAAC;IAC/B,WAAW,GAAW,CAAC,CAAC;IAEhC,YAAY,OAA0B;QACpC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC,qBAAqB;IACzE,CAAC;IAED,KAAK,CAAC,MAAM,CACV,GAAW;QAEX,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAE/C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,qBAAqB,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO,IAAI,CAAC,SAAS,CAAC,GAAe,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,IAAI,CAAC,UAAU,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC,UAAU,CAAC;QACzB,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE3C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,yBAAyB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAClE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAS,CAAC;QAClD,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC;QAE1C,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,UAAU;QACR,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type VerifyTokenOptions } from './verifyToken.js';
|
|
2
|
+
import type { AuthUUser } from './types.js';
|
|
3
|
+
export interface AuthUMiddlewareOptions extends VerifyTokenOptions {
|
|
4
|
+
optional?: boolean;
|
|
5
|
+
}
|
|
6
|
+
type FastifyRequest = {
|
|
7
|
+
headers: Record<string, string | string[] | undefined>;
|
|
8
|
+
authUUser?: AuthUUser;
|
|
9
|
+
};
|
|
10
|
+
type FastifyReply = {
|
|
11
|
+
status: (code: number) => FastifyReply;
|
|
12
|
+
send: (body: unknown) => void;
|
|
13
|
+
};
|
|
14
|
+
type FastifyInstance = {
|
|
15
|
+
decorate: (name: string, value: unknown) => void;
|
|
16
|
+
decorateRequest: (name: string, value: unknown) => void;
|
|
17
|
+
addHook: (hookName: string, handler: (request: FastifyRequest, reply: FastifyReply) => Promise<void>) => void;
|
|
18
|
+
};
|
|
19
|
+
type FastifyPluginCallback = (fastify: FastifyInstance, options: AuthUMiddlewareOptions, done: (err?: Error) => void) => void;
|
|
20
|
+
export declare function createAuthUMiddleware(options: AuthUMiddlewareOptions): FastifyPluginCallback;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;IACvC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,OAAO,EAAE,CACP,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,KACrE,IAAI,CAAC;CACX,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,KACxB,IAAI,CAAC;AAEV,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CA+CvB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { verifyToken } from './verifyToken.js';
|
|
2
|
+
export function createAuthUMiddleware(options) {
|
|
3
|
+
return (fastify, _opts, done) => {
|
|
4
|
+
fastify.decorateRequest('authUUser', null);
|
|
5
|
+
fastify.decorate('verifyAuthU', async (request, reply) => {
|
|
6
|
+
const authHeader = request.headers.authorization;
|
|
7
|
+
if (!authHeader || typeof authHeader !== 'string') {
|
|
8
|
+
if (options.optional) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
reply.status(401).send({ error: 'Missing authorization header' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const [scheme, token] = authHeader.split(' ');
|
|
15
|
+
if (scheme?.toLowerCase() !== 'bearer' || !token) {
|
|
16
|
+
if (options.optional) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
reply
|
|
20
|
+
.status(401)
|
|
21
|
+
.send({ error: 'Invalid authorization header format' });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const verified = await verifyToken(token, options);
|
|
26
|
+
request.authUUser = verified.payload;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (options.optional) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
reply.status(401).send({
|
|
33
|
+
error: 'Invalid token',
|
|
34
|
+
message: err instanceof Error ? err.message : 'Token verification failed'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
done();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAA0B,MAAM,kBAAkB,CAAC;AAgCtE,MAAM,UAAU,qBAAqB,CACnC,OAA+B;IAE/B,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC9B,OAAO,CAAC,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAE3C,OAAO,CAAC,QAAQ,CACd,aAAa,EACb,KAAK,EAAE,OAAuB,EAAE,KAAmB,EAAiB,EAAE;YACpE,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAEjD,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAClD,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,8BAA8B,EAAC,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;YAED,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAE9C,IAAI,MAAM,EAAE,WAAW,EAAE,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;gBACjD,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBACD,KAAK;qBACF,MAAM,CAAC,GAAG,CAAC;qBACX,IAAI,CAAC,EAAC,KAAK,EAAE,qCAAqC,EAAC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACnD,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC;YACvC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACrB,KAAK,EAAE,eAAe;oBACtB,OAAO,EACL,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B;iBACnE,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CACF,CAAC;QAEF,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface AuthUUser {
|
|
2
|
+
sub: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
emailVerified?: boolean;
|
|
5
|
+
name?: string;
|
|
6
|
+
picture?: string;
|
|
7
|
+
scope?: string;
|
|
8
|
+
clientId?: string;
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface VerifiedToken {
|
|
12
|
+
payload: AuthUUser;
|
|
13
|
+
header: {
|
|
14
|
+
alg: string;
|
|
15
|
+
typ?: string;
|
|
16
|
+
kid?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface JWK {
|
|
20
|
+
kty: string;
|
|
21
|
+
kid: string;
|
|
22
|
+
use?: string;
|
|
23
|
+
alg?: string;
|
|
24
|
+
n?: string;
|
|
25
|
+
e?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface JWKS {
|
|
28
|
+
keys: JWK[];
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { JwksClient } from './jwks.js';
|
|
2
|
+
import type { VerifiedToken } from './types.js';
|
|
3
|
+
export interface VerifyTokenOptions {
|
|
4
|
+
domain: string;
|
|
5
|
+
audience?: string;
|
|
6
|
+
issuer?: string;
|
|
7
|
+
jwksClient?: JwksClient;
|
|
8
|
+
}
|
|
9
|
+
export declare function verifyToken(token: string, options: VerifyTokenOptions): Promise<VerifiedToken>;
|
|
10
|
+
//# sourceMappingURL=verifyToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifyToken.d.ts","sourceRoot":"","sources":["../src/verifyToken.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAY,aAAa,EAAC,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAgBD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAuCxB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import { JwksClient } from './jwks.js';
|
|
3
|
+
const clientCache = new Map();
|
|
4
|
+
function getJwksClient(domain) {
|
|
5
|
+
if (!clientCache.has(domain)) {
|
|
6
|
+
clientCache.set(domain, new JwksClient({
|
|
7
|
+
jwksUri: `https://${domain}/.well-known/jwks.json`
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
return clientCache.get(domain);
|
|
11
|
+
}
|
|
12
|
+
export async function verifyToken(token, options) {
|
|
13
|
+
const { domain, audience, issuer } = options;
|
|
14
|
+
const jwksClient = options.jwksClient ?? getJwksClient(domain);
|
|
15
|
+
const expectedIssuer = issuer ?? `https://${domain}`;
|
|
16
|
+
const { payload, protectedHeader } = await jose.jwtVerify(token, async (header) => {
|
|
17
|
+
if (!header.kid) {
|
|
18
|
+
throw new Error('Token missing kid header');
|
|
19
|
+
}
|
|
20
|
+
return jwksClient.getKey(header.kid);
|
|
21
|
+
}, {
|
|
22
|
+
issuer: expectedIssuer,
|
|
23
|
+
audience
|
|
24
|
+
});
|
|
25
|
+
const user = {
|
|
26
|
+
sub: payload.sub,
|
|
27
|
+
email: payload.email,
|
|
28
|
+
emailVerified: payload.email_verified,
|
|
29
|
+
name: payload.name,
|
|
30
|
+
picture: payload.picture,
|
|
31
|
+
scope: payload.scope,
|
|
32
|
+
clientId: payload.client_id,
|
|
33
|
+
tenantId: payload.tenant_id
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
payload: user,
|
|
37
|
+
header: {
|
|
38
|
+
alg: protectedHeader.alg,
|
|
39
|
+
typ: protectedHeader.typ,
|
|
40
|
+
kid: protectedHeader.kid
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=verifyToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifyToken.js","sourceRoot":"","sources":["../src/verifyToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAUrC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;AAElD,SAAS,aAAa,CAAC,MAAc;IACnC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,WAAW,CAAC,GAAG,CACb,MAAM,EACN,IAAI,UAAU,CAAC;YACb,OAAO,EAAE,WAAW,MAAM,wBAAwB;SACnD,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,OAA2B;IAE3B,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAC,GAAG,OAAO,CAAC;IAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;IAE/D,MAAM,cAAc,GAAG,MAAM,IAAI,WAAW,MAAM,EAAE,CAAC;IAErD,MAAM,EAAC,OAAO,EAAE,eAAe,EAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CACrD,KAAK,EACL,KAAK,EAAC,MAAM,EAAC,EAAE;QACb,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC,EACD;QACE,MAAM,EAAE,cAAc;QACtB,QAAQ;KACT,CACF,CAAC;IAEF,MAAM,IAAI,GAAc;QACtB,GAAG,EAAE,OAAO,CAAC,GAAa;QAC1B,KAAK,EAAE,OAAO,CAAC,KAA2B;QAC1C,aAAa,EAAE,OAAO,CAAC,cAAqC;QAC5D,IAAI,EAAE,OAAO,CAAC,IAA0B;QACxC,OAAO,EAAE,OAAO,CAAC,OAA6B;QAC9C,KAAK,EAAE,OAAO,CAAC,KAA2B;QAC1C,QAAQ,EAAE,OAAO,CAAC,SAA+B;QACjD,QAAQ,EAAE,OAAO,CAAC,SAA+B;KAClD,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,MAAM,EAAE;YACN,GAAG,EAAE,eAAe,CAAC,GAAG;YACxB,GAAG,EAAE,eAAe,CAAC,GAAG;YACxB,GAAG,EAAE,eAAe,CAAC,GAAG;SACzB;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authu/node",
|
|
3
|
+
"version": "0.1.18",
|
|
4
|
+
"description": "Node.js SDK for AuthU - Centralized Multi-Tenant Authentication Service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"authu",
|
|
19
|
+
"auth",
|
|
20
|
+
"authentication",
|
|
21
|
+
"oauth2",
|
|
22
|
+
"oidc",
|
|
23
|
+
"node",
|
|
24
|
+
"fastify",
|
|
25
|
+
"jwt",
|
|
26
|
+
"jwks"
|
|
27
|
+
],
|
|
28
|
+
"author": "Uralys",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"jose": "^6.0.0",
|
|
32
|
+
"@authu/shared": "0.1.18"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^9.33.0",
|
|
36
|
+
"@types/node": "^22.10.7",
|
|
37
|
+
"eslint": "^9.33.0",
|
|
38
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
39
|
+
"prettier": "^3.6.2",
|
|
40
|
+
"typescript": "^5.7.3",
|
|
41
|
+
"typescript-eslint": "^8.40.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"dev": "tsc --watch",
|
|
46
|
+
"eslint": "eslint src --cache",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "pnpm run eslint && pnpm run typecheck"
|
|
49
|
+
}
|
|
50
|
+
}
|