@geekmidas/auth 0.0.4 → 0.1.0
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 +343 -214
- package/dist/{cacheTokenStorage-BlpcZwef.d.mts → cacheTokenStorage-BzBRJfd-.d.mts} +2 -1
- package/dist/cacheTokenStorage-Ca05vf3T.mjs +2 -1
- package/dist/cacheTokenStorage-Ca05vf3T.mjs.map +1 -0
- package/dist/cacheTokenStorage-CvptztkW.cjs +2 -1
- package/dist/cacheTokenStorage-CvptztkW.cjs.map +1 -0
- package/dist/cacheTokenStorage-yNUpDD8W.d.cts +2 -1
- package/dist/cacheTokenStorage.d.mts +1 -1
- package/dist/{chunk-DWy1uDak.cjs → chunk-CUT6urMc.cjs} +0 -9
- package/dist/client-DaiUm0UZ.cjs +2 -1
- package/dist/client-DaiUm0UZ.cjs.map +1 -0
- package/dist/client-DjNZS6vZ.mjs +2 -1
- package/dist/client-DjNZS6vZ.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/hono/jwt.cjs +92 -0
- package/dist/hono/jwt.cjs.map +1 -0
- package/dist/hono/jwt.d.cts +45 -0
- package/dist/hono/jwt.d.mts +45 -0
- package/dist/hono/jwt.mjs +91 -0
- package/dist/hono/jwt.mjs.map +1 -0
- package/dist/hono/oidc.cjs +104 -0
- package/dist/hono/oidc.cjs.map +1 -0
- package/dist/hono/oidc.d.cts +48 -0
- package/dist/hono/oidc.d.mts +48 -0
- package/dist/hono/oidc.mjs +103 -0
- package/dist/hono/oidc.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/jwt-Bo_eACEi.cjs +70 -0
- package/dist/jwt-Bo_eACEi.cjs.map +1 -0
- package/dist/jwt-D88cPJyT.mjs +52 -0
- package/dist/jwt-D88cPJyT.mjs.map +1 -0
- package/dist/jwt-DF9kFbmh.d.cts +61 -0
- package/dist/jwt-DH8CJ2xC.d.mts +61 -0
- package/dist/jwt.cjs +5 -0
- package/dist/jwt.d.cts +2 -0
- package/dist/jwt.d.mts +2 -0
- package/dist/jwt.mjs +3 -0
- package/dist/lambda/jwt.cjs +126 -0
- package/dist/lambda/jwt.cjs.map +1 -0
- package/dist/lambda/jwt.d.cts +52 -0
- package/dist/lambda/jwt.d.mts +52 -0
- package/dist/lambda/jwt.mjs +125 -0
- package/dist/lambda/jwt.mjs.map +1 -0
- package/dist/lambda/oidc.cjs +117 -0
- package/dist/lambda/oidc.cjs.map +1 -0
- package/dist/lambda/oidc.d.cts +44 -0
- package/dist/lambda/oidc.d.mts +44 -0
- package/dist/lambda/oidc.mjs +116 -0
- package/dist/lambda/oidc.mjs.map +1 -0
- package/dist/oidc-C0KyYaAK.cjs +68 -0
- package/dist/oidc-C0KyYaAK.cjs.map +1 -0
- package/dist/oidc-CKOGLxjV.d.mts +86 -0
- package/dist/oidc-DDLIB5gb.d.cts +86 -0
- package/dist/oidc-KA30gMlx.mjs +62 -0
- package/dist/oidc-KA30gMlx.mjs.map +1 -0
- package/dist/oidc.cjs +3 -0
- package/dist/oidc.d.cts +3 -0
- package/dist/oidc.d.mts +3 -0
- package/dist/oidc.mjs +3 -0
- package/dist/server.cjs +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/{tokenManager-CCcFk2Py.d.mts → tokenManager--Q93eNYr.d.mts} +2 -1
- package/dist/tokenManager-BVXxfHRa.mjs +2 -1
- package/dist/tokenManager-BVXxfHRa.mjs.map +1 -0
- package/dist/{tokenManager-B60gFnEF.cjs → tokenManager-EPmwgai2.cjs} +3 -2
- package/dist/tokenManager-EPmwgai2.cjs.map +1 -0
- package/dist/{tokenManager-mpzJKLvX.d.cts → tokenManager-H6-lWEah.d.cts} +2 -1
- package/dist/tokenManager.cjs +1 -1
- package/dist/tokenManager.d.cts +1 -1
- package/dist/tokenManager.d.mts +1 -1
- package/package.json +56 -4
- package/src/__tests__/hono-jwt.spec.ts +232 -0
- package/src/__tests__/hono-oidc.spec.ts +605 -0
- package/src/__tests__/jwt.spec.ts +174 -0
- package/src/__tests__/lambda-jwt.spec.ts +278 -0
- package/src/__tests__/lambda-oidc.spec.ts +623 -0
- package/src/__tests__/oidc.spec.ts +405 -0
- package/src/hono/jwt.ts +142 -0
- package/src/hono/oidc.ts +170 -0
- package/src/jwt.ts +106 -0
- package/src/lambda/jwt.ts +210 -0
- package/src/lambda/oidc.ts +203 -0
- package/src/oidc.ts +156 -0
- package/dist/__tests__/cacheTokenStorage.spec.cjs +0 -146
- package/dist/__tests__/cacheTokenStorage.spec.d.cts +0 -1
- package/dist/__tests__/cacheTokenStorage.spec.d.mts +0 -1
- package/dist/__tests__/cacheTokenStorage.spec.mjs +0 -145
- package/dist/__tests__/client.spec.cjs +0 -17281
- package/dist/__tests__/client.spec.d.cts +0 -1
- package/dist/__tests__/client.spec.d.mts +0 -1
- package/dist/__tests__/client.spec.mjs +0 -17308
- package/dist/__tests__/tokenManager.spec.cjs +0 -197
- package/dist/__tests__/tokenManager.spec.d.cts +0 -1
- package/dist/__tests__/tokenManager.spec.d.mts +0 -1
- package/dist/__tests__/tokenManager.spec.mjs +0 -196
- package/dist/playground.cjs +0 -0
- package/dist/playground.d.cts +0 -1
- package/dist/playground.d.mts +0 -1
- package/dist/playground.mjs +0 -0
- package/src/playground.ts +0 -0
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# @geekmidas/auth
|
|
2
2
|
|
|
3
|
-
A comprehensive authentication library providing JWT token
|
|
3
|
+
A comprehensive authentication library providing JWT and OIDC token verification, Hono middleware, AWS Lambda authorizers, and client-side token management for TypeScript applications.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **JWT
|
|
7
|
+
- **JWT Verification**: Verify tokens using secrets or JWKS endpoints
|
|
8
|
+
- **OIDC Support**: Auto-discovery from `.well-known/openid-configuration`
|
|
9
|
+
- **Hono Middleware**: Ready-to-use middleware for Hono applications
|
|
10
|
+
- **Lambda Authorizers**: TOKEN and REQUEST authorizers for AWS API Gateway
|
|
11
|
+
- **Token Management**: Client-side token storage and automatic refresh
|
|
12
|
+
- **Type-Safe**: Full TypeScript support with generic claims types
|
|
8
13
|
- **Multiple Storage Options**: LocalStorage, Memory, and Cache-based token storage
|
|
9
|
-
- **Automatic Token Refresh**: Seamless token refresh with expiration handling
|
|
10
|
-
- **Type-Safe**: Full TypeScript support with complete type inference
|
|
11
|
-
- **Framework Agnostic**: Works with any JavaScript framework or vanilla JS
|
|
12
|
-
- **OpenAuth Integration**: Built on top of @openauthjs/openauth for standards compliance
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -21,26 +22,274 @@ pnpm add @geekmidas/auth
|
|
|
21
22
|
|
|
22
23
|
The package provides multiple entry points for different use cases:
|
|
23
24
|
|
|
24
|
-
- `@geekmidas/auth` -
|
|
25
|
+
- `@geekmidas/auth/jwt` - JWT verification
|
|
26
|
+
- `@geekmidas/auth/oidc` - OIDC verification with auto-discovery
|
|
27
|
+
- `@geekmidas/auth/hono/jwt` - Hono JWT middleware
|
|
28
|
+
- `@geekmidas/auth/hono/oidc` - Hono OIDC middleware
|
|
29
|
+
- `@geekmidas/auth/lambda/jwt` - Lambda JWT authorizer
|
|
30
|
+
- `@geekmidas/auth/lambda/oidc` - Lambda OIDC authorizer
|
|
25
31
|
- `@geekmidas/auth/client` - Client-side token management
|
|
26
32
|
- `@geekmidas/auth/server` - Server-side token management
|
|
27
33
|
|
|
28
|
-
##
|
|
34
|
+
## JWT Verification
|
|
35
|
+
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { JwtVerifier } from '@geekmidas/auth/jwt';
|
|
40
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
41
|
+
|
|
42
|
+
// Parse environment configuration
|
|
43
|
+
const env = new EnvironmentParser(process.env)
|
|
44
|
+
.create((get) => ({
|
|
45
|
+
jwt: {
|
|
46
|
+
secret: get('JWT_SECRET').string(),
|
|
47
|
+
issuer: get('JWT_ISSUER').string().optional(),
|
|
48
|
+
audience: get('JWT_AUDIENCE').string().optional(),
|
|
49
|
+
},
|
|
50
|
+
}))
|
|
51
|
+
.parse();
|
|
52
|
+
|
|
53
|
+
// With secret (HS256)
|
|
54
|
+
const verifier = new JwtVerifier({
|
|
55
|
+
secret: env.jwt.secret,
|
|
56
|
+
issuer: env.jwt.issuer,
|
|
57
|
+
audience: env.jwt.audience,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const claims = await verifier.verify(token);
|
|
61
|
+
console.log('User:', claims.sub);
|
|
62
|
+
|
|
63
|
+
// Returns null instead of throwing for invalid tokens
|
|
64
|
+
const claimsOrNull = await verifier.verifyOrNull(token);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### With JWKS (RS256, ES256, etc.)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { JwtVerifier } from '@geekmidas/auth/jwt';
|
|
71
|
+
|
|
72
|
+
const verifier = new JwtVerifier({
|
|
73
|
+
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
|
|
74
|
+
issuer: 'https://auth.example.com',
|
|
75
|
+
audience: 'my-api',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const claims = await verifier.verify(token);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Custom Claims Type
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface MyClaims {
|
|
85
|
+
sub: string;
|
|
86
|
+
role: string;
|
|
87
|
+
permissions: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const verifier = new JwtVerifier<MyClaims>({
|
|
91
|
+
secret: env.jwt.secret, // From envkit parser
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const claims = await verifier.verify(token);
|
|
95
|
+
console.log('Role:', claims.role); // Typed!
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Decode Without Verification
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { decodeJwt } from '@geekmidas/auth/jwt';
|
|
102
|
+
|
|
103
|
+
// Decode token without verification (useful for debugging)
|
|
104
|
+
const claims = decodeJwt(token);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## OIDC Verification
|
|
108
|
+
|
|
109
|
+
OIDC verifier automatically discovers JWKS URI and other configuration from the issuer's `.well-known/openid-configuration` endpoint.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { OidcVerifier } from '@geekmidas/auth/oidc';
|
|
113
|
+
|
|
114
|
+
const verifier = new OidcVerifier({
|
|
115
|
+
issuer: 'https://auth.example.com',
|
|
116
|
+
audience: 'my-client-id',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Verify token (auto-discovers JWKS)
|
|
120
|
+
const claims = await verifier.verify(token);
|
|
121
|
+
|
|
122
|
+
// Fetch user info from userinfo endpoint
|
|
123
|
+
const userInfo = await verifier.fetchUserInfo(token);
|
|
124
|
+
console.log('Email:', userInfo?.email);
|
|
125
|
+
|
|
126
|
+
// Get discovery document
|
|
127
|
+
const discovery = await verifier.getDiscovery();
|
|
128
|
+
console.log('Token endpoint:', discovery.token_endpoint);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Hono Middleware
|
|
132
|
+
|
|
133
|
+
### JWT Middleware
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Hono } from 'hono';
|
|
137
|
+
import { JwtMiddleware } from '@geekmidas/auth/hono/jwt';
|
|
138
|
+
|
|
139
|
+
const app = new Hono();
|
|
140
|
+
|
|
141
|
+
const jwt = new JwtMiddleware({
|
|
142
|
+
config: {
|
|
143
|
+
secret: env.jwt.secret,
|
|
144
|
+
issuer: env.jwt.issuer,
|
|
145
|
+
},
|
|
146
|
+
contextKey: 'jwtClaims', // Where to store claims in context
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Protected routes
|
|
150
|
+
app.use('/api/*', jwt.handler());
|
|
151
|
+
|
|
152
|
+
app.get('/api/profile', (c) => {
|
|
153
|
+
const claims = c.get('jwtClaims');
|
|
154
|
+
return c.json({ userId: claims.sub });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Optional authentication (doesn't fail if no token)
|
|
158
|
+
app.use('/public/*', jwt.optional());
|
|
159
|
+
|
|
160
|
+
app.get('/public/posts', (c) => {
|
|
161
|
+
const claims = c.get('jwtClaims'); // May be undefined
|
|
162
|
+
return c.json({ authenticated: !!claims });
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### OIDC Middleware
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { OidcMiddleware } from '@geekmidas/auth/hono/oidc';
|
|
170
|
+
|
|
171
|
+
const oidc = new OidcMiddleware({
|
|
172
|
+
config: {
|
|
173
|
+
issuer: 'https://auth.example.com',
|
|
174
|
+
audience: 'my-client-id',
|
|
175
|
+
},
|
|
176
|
+
fetchUserInfo: true, // Also fetch user info
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
app.use('/api/*', oidc.handler());
|
|
180
|
+
|
|
181
|
+
app.get('/api/profile', (c) => {
|
|
182
|
+
const claims = c.get('oidcClaims');
|
|
183
|
+
const userInfo = c.get('oidcUserInfo');
|
|
184
|
+
return c.json({ sub: claims.sub, email: userInfo?.email });
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Custom Error Handling
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const jwt = new JwtMiddleware({
|
|
192
|
+
config: { secret: env.jwt.secret },
|
|
193
|
+
onError: (c, error) => {
|
|
194
|
+
console.error('Auth error:', error.message);
|
|
195
|
+
return c.json({ error: 'Authentication failed' }, 401);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Token Extraction Options
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const jwt = new JwtMiddleware({
|
|
204
|
+
config: { secret: env.jwt.secret },
|
|
205
|
+
extraction: {
|
|
206
|
+
headerName: 'x-auth-token', // Custom header (default: 'authorization')
|
|
207
|
+
tokenPrefix: 'Token ', // Custom prefix (default: 'Bearer ')
|
|
208
|
+
cookieName: 'auth_token', // Also check cookies
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Lambda Authorizers
|
|
214
|
+
|
|
215
|
+
### JWT Authorizer
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { JwtAuthorizer } from '@geekmidas/auth/lambda/jwt';
|
|
219
|
+
|
|
220
|
+
const authorizer = new JwtAuthorizer({
|
|
221
|
+
config: {
|
|
222
|
+
secret: env.jwt.secret,
|
|
223
|
+
issuer: env.jwt.issuer,
|
|
224
|
+
},
|
|
225
|
+
// Extract principal ID from claims
|
|
226
|
+
getPrincipalId: (claims) => claims.sub ?? 'unknown',
|
|
227
|
+
// Add claims to request context
|
|
228
|
+
getContext: (claims) => ({
|
|
229
|
+
userId: claims.sub!,
|
|
230
|
+
role: claims.role,
|
|
231
|
+
}),
|
|
232
|
+
// Custom authorization logic
|
|
233
|
+
authorize: async (claims) => {
|
|
234
|
+
return claims.role === 'admin';
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// For TOKEN authorizers (API Gateway v1)
|
|
239
|
+
export const tokenHandler = authorizer.tokenHandler();
|
|
240
|
+
|
|
241
|
+
// For REQUEST authorizers (API Gateway v1/v2)
|
|
242
|
+
export const requestHandler = authorizer.requestHandler();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### OIDC Authorizer
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { OidcAuthorizer } from '@geekmidas/auth/lambda/oidc';
|
|
249
|
+
|
|
250
|
+
const authorizer = new OidcAuthorizer({
|
|
251
|
+
config: {
|
|
252
|
+
issuer: 'https://auth.example.com',
|
|
253
|
+
audience: 'my-api',
|
|
254
|
+
},
|
|
255
|
+
getContext: (claims) => ({
|
|
256
|
+
userId: claims.sub!,
|
|
257
|
+
email: claims.email,
|
|
258
|
+
}),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export const handler = authorizer.requestHandler();
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Token Extraction for REQUEST Authorizers
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const authorizer = new JwtAuthorizer({
|
|
268
|
+
config: { secret: env.jwt.secret },
|
|
269
|
+
extraction: {
|
|
270
|
+
headerName: 'authorization',
|
|
271
|
+
tokenPrefix: 'Bearer ',
|
|
272
|
+
cookieName: 'auth_token', // Also check cookies
|
|
273
|
+
},
|
|
274
|
+
// Use specific resource ARN vs wildcard
|
|
275
|
+
wildcardResource: true, // Default: true (enables caching)
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Client-Side Token Management
|
|
29
280
|
|
|
30
281
|
### Basic Token Client
|
|
31
282
|
|
|
32
283
|
```typescript
|
|
33
284
|
import { TokenClient, LocalStorageTokenStorage } from '@geekmidas/auth/client';
|
|
34
285
|
|
|
35
|
-
// Create a token client with localStorage
|
|
36
286
|
const client = new TokenClient({
|
|
37
287
|
storage: new LocalStorageTokenStorage(),
|
|
38
288
|
refreshEndpoint: '/api/auth/refresh',
|
|
39
289
|
onTokenRefresh: (tokens) => {
|
|
40
|
-
console.log('Tokens refreshed
|
|
290
|
+
console.log('Tokens refreshed');
|
|
41
291
|
},
|
|
42
292
|
onTokenExpired: () => {
|
|
43
|
-
console.log('Tokens expired, redirect to login');
|
|
44
293
|
window.location.href = '/login';
|
|
45
294
|
},
|
|
46
295
|
});
|
|
@@ -85,65 +334,32 @@ import { CacheTokenStorage } from '@geekmidas/auth/client';
|
|
|
85
334
|
import { InMemoryCache } from '@geekmidas/cache/memory';
|
|
86
335
|
|
|
87
336
|
const cache = new InMemoryCache<string>();
|
|
88
|
-
const storage = new CacheTokenStorage(
|
|
89
|
-
cache,
|
|
90
|
-
'access_token', // Access token key
|
|
91
|
-
'refresh_token' // Refresh token key
|
|
92
|
-
);
|
|
337
|
+
const storage = new CacheTokenStorage(cache);
|
|
93
338
|
|
|
94
339
|
// Supports TTL for automatic expiration
|
|
95
340
|
await storage.setAccessToken('token', 3600); // 1 hour TTL
|
|
96
341
|
```
|
|
97
342
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// Check if a token is expired
|
|
102
|
-
const isExpired = client.isTokenExpired(token);
|
|
103
|
-
|
|
104
|
-
// Get token expiration date
|
|
105
|
-
const expiration = client.getTokenExpiration(token);
|
|
106
|
-
console.log('Token expires at:', expiration);
|
|
107
|
-
|
|
108
|
-
// Get a valid token (refreshes automatically if needed)
|
|
109
|
-
const validToken = await client.getValidAccessToken();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### HTTP Client Integration
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import { TokenClient } from '@geekmidas/auth/client';
|
|
116
|
-
|
|
117
|
-
class ApiClient {
|
|
118
|
-
constructor(private tokenClient: TokenClient) {}
|
|
119
|
-
|
|
120
|
-
async makeRequest(url: string, options: RequestInit = {}) {
|
|
121
|
-
const headers = await this.tokenClient.createValidAuthHeaders();
|
|
122
|
-
|
|
123
|
-
return fetch(url, {
|
|
124
|
-
...options,
|
|
125
|
-
headers: {
|
|
126
|
-
...options.headers,
|
|
127
|
-
...headers,
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const apiClient = new ApiClient(tokenClient);
|
|
134
|
-
const response = await apiClient.makeRequest('/api/users');
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Server-Side Usage
|
|
343
|
+
## Server-Side Token Management
|
|
138
344
|
|
|
139
345
|
### Token Manager
|
|
140
346
|
|
|
141
347
|
```typescript
|
|
142
348
|
import { TokenManager } from '@geekmidas/auth/server';
|
|
349
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
350
|
+
|
|
351
|
+
const env = new EnvironmentParser(process.env)
|
|
352
|
+
.create((get) => ({
|
|
353
|
+
auth: {
|
|
354
|
+
accessTokenSecret: get('ACCESS_TOKEN_SECRET').string(),
|
|
355
|
+
refreshTokenSecret: get('REFRESH_TOKEN_SECRET').string(),
|
|
356
|
+
},
|
|
357
|
+
}))
|
|
358
|
+
.parse();
|
|
143
359
|
|
|
144
360
|
const tokenManager = new TokenManager({
|
|
145
|
-
accessTokenSecret:
|
|
146
|
-
refreshTokenSecret:
|
|
361
|
+
accessTokenSecret: env.auth.accessTokenSecret,
|
|
362
|
+
refreshTokenSecret: env.auth.refreshTokenSecret,
|
|
147
363
|
accessTokenExpiresIn: '15m',
|
|
148
364
|
refreshTokenExpiresIn: '7d',
|
|
149
365
|
});
|
|
@@ -156,207 +372,120 @@ const tokens = tokenManager.generateTokenPair({
|
|
|
156
372
|
});
|
|
157
373
|
|
|
158
374
|
// Verify tokens
|
|
159
|
-
|
|
160
|
-
const payload = tokenManager.verifyAccessToken(accessToken);
|
|
161
|
-
console.log('User ID:', payload.userId);
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('Invalid token:', error.message);
|
|
164
|
-
}
|
|
375
|
+
const payload = tokenManager.verifyAccessToken(accessToken);
|
|
165
376
|
|
|
166
377
|
// Refresh access token
|
|
167
|
-
|
|
168
|
-
const newAccessToken = tokenManager.refreshAccessToken(refreshToken);
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('Refresh failed:', error.message);
|
|
171
|
-
}
|
|
378
|
+
const newAccessToken = tokenManager.refreshAccessToken(refreshToken);
|
|
172
379
|
```
|
|
173
380
|
|
|
174
|
-
|
|
381
|
+
## API Reference
|
|
175
382
|
|
|
176
|
-
|
|
177
|
-
import { TokenManager } from '@geekmidas/auth/server';
|
|
383
|
+
### JwtVerifier
|
|
178
384
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
385
|
+
```typescript
|
|
386
|
+
class JwtVerifier<TClaims extends JwtClaims = JwtClaims> {
|
|
387
|
+
constructor(config: JwtConfig);
|
|
388
|
+
verify(token: string): Promise<TClaims>;
|
|
389
|
+
verifyOrNull(token: string): Promise<TClaims | null>;
|
|
390
|
+
clearCache(): void;
|
|
391
|
+
}
|
|
183
392
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const payload = tokenManager.verifyAccessToken(token);
|
|
194
|
-
req.user = payload;
|
|
195
|
-
next();
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return res.status(403).json({ error: 'Invalid or expired token' });
|
|
198
|
-
}
|
|
393
|
+
interface JwtConfig {
|
|
394
|
+
secret?: string; // For HS256
|
|
395
|
+
jwksUri?: string; // For RS256, ES256, etc.
|
|
396
|
+
issuer?: string; // Expected issuer
|
|
397
|
+
audience?: string; // Expected audience
|
|
398
|
+
algorithms?: string[]; // Allowed algorithms
|
|
199
399
|
}
|
|
200
400
|
```
|
|
201
401
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
### TokenClient
|
|
205
|
-
|
|
206
|
-
The main client for managing tokens on the client-side.
|
|
207
|
-
|
|
208
|
-
#### Constructor Options
|
|
402
|
+
### OidcVerifier
|
|
209
403
|
|
|
210
404
|
```typescript
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
onTokenExpired?: () => void; // Callback when tokens expire
|
|
405
|
+
class OidcVerifier<TClaims, TUserInfo> {
|
|
406
|
+
constructor(config: OidcConfig);
|
|
407
|
+
verify(token: string): Promise<TClaims>;
|
|
408
|
+
verifyOrNull(token: string): Promise<TClaims | null>;
|
|
409
|
+
fetchUserInfo(token: string): Promise<TUserInfo | null>;
|
|
410
|
+
getDiscovery(): Promise<OidcDiscovery>;
|
|
411
|
+
clearCache(): void;
|
|
219
412
|
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
#### Methods
|
|
223
|
-
|
|
224
|
-
- `getAccessToken(): Promise<string | null>` - Get stored access token
|
|
225
|
-
- `getRefreshToken(): Promise<string | null>` - Get stored refresh token
|
|
226
|
-
- `setTokens(accessToken, refreshToken?, accessTtl?, refreshTtl?): Promise<void>` - Store tokens
|
|
227
|
-
- `clearTokens(): Promise<void>` - Clear all stored tokens
|
|
228
|
-
- `isTokenExpired(token: string): boolean` - Check if token is expired
|
|
229
|
-
- `getTokenExpiration(token: string): Date | null` - Get token expiration date
|
|
230
|
-
- `refreshTokens(): Promise<boolean>` - Refresh tokens via API
|
|
231
|
-
- `getValidAccessToken(): Promise<string | null>` - Get valid token (auto-refresh)
|
|
232
|
-
- `createAuthHeaders(): Promise<Record<string, string>>` - Create auth headers
|
|
233
|
-
- `createValidAuthHeaders(): Promise<Record<string, string>>` - Create auth headers with valid token
|
|
234
413
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
getAccessToken(): Promise<string | null> | string | null;
|
|
240
|
-
setAccessToken(token: string, ttl?: number): Promise<void> | void;
|
|
241
|
-
getRefreshToken(): Promise<string | null> | string | null;
|
|
242
|
-
setRefreshToken(token: string, ttl?: number): Promise<void> | void;
|
|
243
|
-
clearTokens(): Promise<void> | void;
|
|
414
|
+
interface OidcConfig {
|
|
415
|
+
issuer: string; // OIDC issuer URL
|
|
416
|
+
audience?: string; // Expected audience
|
|
417
|
+
algorithms?: string[]; // Allowed algorithms
|
|
244
418
|
}
|
|
245
419
|
```
|
|
246
420
|
|
|
247
|
-
###
|
|
248
|
-
|
|
249
|
-
Server-side JWT token management.
|
|
250
|
-
|
|
251
|
-
#### Constructor Options
|
|
421
|
+
### JwtMiddleware / OidcMiddleware
|
|
252
422
|
|
|
253
423
|
```typescript
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
refreshTokenExpiresIn?: string; // Refresh token expiration (default: '7d')
|
|
424
|
+
class JwtMiddleware<TClaims> {
|
|
425
|
+
constructor(options: JwtMiddlewareOptions<TClaims>);
|
|
426
|
+
handler(): MiddlewareHandler; // Required auth
|
|
427
|
+
optional(): MiddlewareHandler; // Optional auth
|
|
259
428
|
}
|
|
260
|
-
```
|
|
261
429
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
- `getTokenExpiration(token: string): Date | null` - Get token expiration date
|
|
430
|
+
interface JwtMiddlewareOptions<TClaims> {
|
|
431
|
+
config: JwtConfig;
|
|
432
|
+
extraction?: TokenExtractionOptions;
|
|
433
|
+
contextKey?: string;
|
|
434
|
+
onError?: (c: Context, error: Error) => Response;
|
|
435
|
+
transformClaims?: (claims: JwtClaims) => TClaims;
|
|
436
|
+
}
|
|
437
|
+
```
|
|
271
438
|
|
|
272
|
-
###
|
|
439
|
+
### JwtAuthorizer / OidcAuthorizer
|
|
273
440
|
|
|
274
441
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
interface TokenPair {
|
|
282
|
-
accessToken: string;
|
|
283
|
-
refreshToken: string;
|
|
442
|
+
class JwtAuthorizer<TClaims> {
|
|
443
|
+
constructor(options: JwtAuthorizerOptions<TClaims>);
|
|
444
|
+
tokenHandler(): Handler; // TOKEN authorizer
|
|
445
|
+
requestHandler(): Handler; // REQUEST authorizer
|
|
284
446
|
}
|
|
285
447
|
|
|
286
|
-
interface
|
|
287
|
-
|
|
288
|
-
|
|
448
|
+
interface JwtAuthorizerOptions<TClaims> {
|
|
449
|
+
config: JwtConfig;
|
|
450
|
+
extraction?: TokenExtractionOptions;
|
|
451
|
+
wildcardResource?: boolean;
|
|
452
|
+
getPrincipalId?: (claims: TClaims) => string;
|
|
453
|
+
getContext?: (claims: TClaims) => Record<string, string | number | boolean>;
|
|
454
|
+
authorize?: (claims: TClaims) => boolean | Promise<boolean>;
|
|
289
455
|
}
|
|
290
456
|
```
|
|
291
457
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
The library provides descriptive error messages for common scenarios:
|
|
458
|
+
### TokenExtractionOptions
|
|
295
459
|
|
|
296
460
|
```typescript
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// Handle invalid token
|
|
302
|
-
} else if (error.message.includes('expired')) {
|
|
303
|
-
// Handle expired token
|
|
304
|
-
}
|
|
461
|
+
interface TokenExtractionOptions {
|
|
462
|
+
headerName?: string; // Default: 'authorization'
|
|
463
|
+
tokenPrefix?: string; // Default: 'Bearer '
|
|
464
|
+
cookieName?: string; // Optional cookie fallback
|
|
305
465
|
}
|
|
306
466
|
```
|
|
307
467
|
|
|
308
468
|
## Security Best Practices
|
|
309
469
|
|
|
310
|
-
1. **Use Strong Secrets**: Ensure your JWT secrets are cryptographically secure
|
|
470
|
+
1. **Use Strong Secrets**: Ensure your JWT secrets are cryptographically secure.
|
|
311
471
|
|
|
312
472
|
2. **Short Access Token Expiration**: Keep access tokens short-lived (15 minutes recommended).
|
|
313
473
|
|
|
314
|
-
3. **
|
|
315
|
-
- Browser: LocalStorage or secure cookies
|
|
316
|
-
- Server: Memory or secure cache
|
|
317
|
-
- Mobile: Secure keychain/keystore
|
|
318
|
-
|
|
319
|
-
4. **HTTPS Only**: Always use HTTPS in production to prevent token interception.
|
|
320
|
-
|
|
321
|
-
5. **Token Rotation**: Implement refresh token rotation for enhanced security.
|
|
322
|
-
|
|
323
|
-
## Testing
|
|
324
|
-
|
|
325
|
-
The package includes comprehensive test utilities:
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
import { MemoryTokenStorage, TokenClient } from '@geekmidas/auth/client';
|
|
329
|
-
|
|
330
|
-
// Use memory storage for tests
|
|
331
|
-
const storage = new MemoryTokenStorage();
|
|
332
|
-
const client = new TokenClient({ storage });
|
|
333
|
-
|
|
334
|
-
// Mock refresh endpoint with MSW
|
|
335
|
-
import { http, HttpResponse } from 'msw';
|
|
336
|
-
|
|
337
|
-
const server = setupServer(
|
|
338
|
-
http.post('/auth/refresh', () => {
|
|
339
|
-
return HttpResponse.json({
|
|
340
|
-
accessToken: 'new-access-token',
|
|
341
|
-
refreshToken: 'new-refresh-token',
|
|
342
|
-
});
|
|
343
|
-
}),
|
|
344
|
-
);
|
|
345
|
-
```
|
|
474
|
+
3. **Use JWKS for Production**: Prefer JWKS over shared secrets for better key rotation.
|
|
346
475
|
|
|
347
|
-
|
|
476
|
+
4. **Validate Claims**: Always validate issuer and audience claims.
|
|
348
477
|
|
|
349
|
-
|
|
478
|
+
5. **HTTPS Only**: Always use HTTPS in production to prevent token interception.
|
|
350
479
|
|
|
351
|
-
|
|
480
|
+
6. **Token Rotation**: Implement refresh token rotation for enhanced security.
|
|
352
481
|
|
|
353
482
|
## Dependencies
|
|
354
483
|
|
|
355
|
-
-
|
|
356
|
-
- `@geekmidas/cache` - Cache storage support
|
|
357
|
-
- `
|
|
358
|
-
- `@types/
|
|
484
|
+
- `jose` - JWT/JWS/JWE operations
|
|
485
|
+
- `@geekmidas/cache` - Cache storage support (optional)
|
|
486
|
+
- `hono` - Hono framework (peer dependency for middleware)
|
|
487
|
+
- `@types/aws-lambda` - AWS Lambda types (peer dependency for authorizers)
|
|
359
488
|
|
|
360
489
|
## License
|
|
361
490
|
|
|
362
|
-
MIT License - see the LICENSE file for details.
|
|
491
|
+
MIT License - see the LICENSE file for details.
|
|
@@ -67,4 +67,5 @@ declare class CacheTokenStorage implements TokenStorage {
|
|
|
67
67
|
clearTokens(): Promise<void>;
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
-
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
|
70
|
+
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
|
71
|
+
//# sourceMappingURL=cacheTokenStorage-BzBRJfd-.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheTokenStorage-Ca05vf3T.mjs","names":["cache: Cache<string>","accessTokenKey: string","refreshTokenKey: string","token: string","ttl?: number"],"sources":["../src/cacheTokenStorage.ts"],"sourcesContent":["import type { Cache } from '@geekmidas/cache';\nimport type { TokenStorage } from './client.js';\n\nexport class CacheTokenStorage implements TokenStorage {\n private cache: Cache<string>;\n private accessTokenKey: string;\n private refreshTokenKey: string;\n\n constructor(\n cache: Cache<string>,\n accessTokenKey: string = 'access_token',\n refreshTokenKey: string = 'refresh_token',\n ) {\n this.cache = cache;\n this.accessTokenKey = accessTokenKey;\n this.refreshTokenKey = refreshTokenKey;\n }\n\n async getAccessToken(): Promise<string | null> {\n const token = await this.cache.get(this.accessTokenKey);\n return token || null;\n }\n\n async setAccessToken(token: string, ttl?: number): Promise<void> {\n await this.cache.set(this.accessTokenKey, token, ttl);\n }\n\n async getRefreshToken(): Promise<string | null> {\n const token = await this.cache.get(this.refreshTokenKey);\n return token || null;\n }\n\n async setRefreshToken(token: string, ttl?: number): Promise<void> {\n await this.cache.set(this.refreshTokenKey, token, ttl);\n }\n\n async clearTokens(): Promise<void> {\n await Promise.all([\n this.cache.delete(this.accessTokenKey),\n this.cache.delete(this.refreshTokenKey),\n ]);\n }\n}\n"],"mappings":";AAGA,IAAa,oBAAb,MAAuD;CACrD,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YACEA,OACAC,iBAAyB,gBACzBC,kBAA0B,iBAC1B;AACA,OAAK,QAAQ;AACb,OAAK,iBAAiB;AACtB,OAAK,kBAAkB;CACxB;CAED,MAAM,iBAAyC;EAC7C,MAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,KAAK,eAAe;AACvD,SAAO,SAAS;CACjB;CAED,MAAM,eAAeC,OAAeC,KAA6B;AAC/D,QAAM,KAAK,MAAM,IAAI,KAAK,gBAAgB,OAAO,IAAI;CACtD;CAED,MAAM,kBAA0C;EAC9C,MAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,KAAK,gBAAgB;AACxD,SAAO,SAAS;CACjB;CAED,MAAM,gBAAgBD,OAAeC,KAA6B;AAChE,QAAM,KAAK,MAAM,IAAI,KAAK,iBAAiB,OAAO,IAAI;CACvD;CAED,MAAM,cAA6B;AACjC,QAAM,QAAQ,IAAI,CAChB,KAAK,MAAM,OAAO,KAAK,eAAe,EACtC,KAAK,MAAM,OAAO,KAAK,gBAAgB,AACxC,EAAC;CACH;AACF"}
|