netreq-auth 0.1.1
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 +191 -0
- package/dist/Auth.d.ts +180 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +440 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Storage.d.ts +146 -0
- package/dist/Storage.d.ts.map +1 -0
- package/dist/Storage.js +193 -0
- package/dist/Storage.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @netreq/auth
|
|
2
|
+
|
|
3
|
+
JWT authentication plugin for netreq. Handles tokens, auto-refresh, and storage.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @netreq/auth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createClient } from 'netreq';
|
|
15
|
+
import { Auth, WebStorage } from '@netreq/auth';
|
|
16
|
+
|
|
17
|
+
const auth = new Auth({
|
|
18
|
+
storage: new WebStorage('localStorage'),
|
|
19
|
+
refreshEndpoint: '/api/auth/refresh',
|
|
20
|
+
onSessionExpired: () => {
|
|
21
|
+
window.location.href = '/login';
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const client = createClient({
|
|
26
|
+
baseUrl: 'https://api.example.com'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
client.use(auth.middleware());
|
|
30
|
+
|
|
31
|
+
// Login
|
|
32
|
+
await auth.login('/api/auth/login', {
|
|
33
|
+
email: 'user@example.com',
|
|
34
|
+
password: 'secret'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// All requests now include Authorization header
|
|
38
|
+
// 401 responses trigger automatic token refresh
|
|
39
|
+
const user = await client.get('/me');
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
1. **Auto Header Injection** - Adds `Authorization: Bearer <token>` to every request
|
|
45
|
+
2. **Token Refresh** - On 401 error, automatically refreshes token and retries the request
|
|
46
|
+
3. **Request Queue** - While refreshing, other requests wait in queue. After refresh, all retry with new token.
|
|
47
|
+
4. **Storage Adapter** - Tokens persist in localStorage/sessionStorage/memory/cookies
|
|
48
|
+
|
|
49
|
+
## Storage Options
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Auth, MemoryStorage, WebStorage, CookieStorage } from '@netreq/auth';
|
|
53
|
+
|
|
54
|
+
// Browser with persistence
|
|
55
|
+
const auth = new Auth({
|
|
56
|
+
storage: new WebStorage('localStorage', 'myapp_tokens'),
|
|
57
|
+
refreshEndpoint: '/api/auth/refresh'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Server-side / SSR
|
|
61
|
+
const auth = new Auth({
|
|
62
|
+
storage: new MemoryStorage(),
|
|
63
|
+
refreshEndpoint: '/api/auth/refresh'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Cookies
|
|
67
|
+
const auth = new Auth({
|
|
68
|
+
storage: new CookieStorage({
|
|
69
|
+
name: 'auth',
|
|
70
|
+
secure: true,
|
|
71
|
+
sameSite: 'strict'
|
|
72
|
+
}),
|
|
73
|
+
refreshEndpoint: '/api/auth/refresh'
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const auth = new Auth({
|
|
81
|
+
// Required
|
|
82
|
+
refreshEndpoint: '/api/auth/refresh',
|
|
83
|
+
|
|
84
|
+
// Optional
|
|
85
|
+
storage: new WebStorage('localStorage'),
|
|
86
|
+
refreshMethod: 'POST',
|
|
87
|
+
authHeaderName: 'Authorization',
|
|
88
|
+
tokenPrefix: 'Bearer ',
|
|
89
|
+
maxRefreshRetries: 3,
|
|
90
|
+
refreshTimeout: 10000,
|
|
91
|
+
|
|
92
|
+
// Transform refresh request/response
|
|
93
|
+
buildRefreshBody: (refreshToken) => ({ token: refreshToken }),
|
|
94
|
+
extractTokenPair: (response) => ({
|
|
95
|
+
accessToken: response.data.access_token,
|
|
96
|
+
refreshToken: response.data.refresh_token
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
// Events
|
|
100
|
+
onLogin: () => console.log('Logged in'),
|
|
101
|
+
onLogout: () => console.log('Logged out'),
|
|
102
|
+
onTokenRefreshed: () => console.log('Token refreshed'),
|
|
103
|
+
onSessionExpired: () => {
|
|
104
|
+
// Redirect to login
|
|
105
|
+
window.location.href = '/login';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## API Methods
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Check auth status
|
|
114
|
+
auth.isAuthenticated(); // boolean
|
|
115
|
+
auth.getAccessToken(); // string | null
|
|
116
|
+
|
|
117
|
+
// Manual login/logout
|
|
118
|
+
await auth.login('/api/login', { email, password });
|
|
119
|
+
await auth.logout();
|
|
120
|
+
|
|
121
|
+
// Manual token management
|
|
122
|
+
await auth.setTokens({
|
|
123
|
+
accessToken: 'xxx',
|
|
124
|
+
refreshToken: 'yyy'
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Custom Storage
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import type { TokenStorage, TokenPair } from '@netreq/auth';
|
|
132
|
+
|
|
133
|
+
class EncryptedStorage implements TokenStorage {
|
|
134
|
+
async getTokens(): Promise<TokenPair | null> {
|
|
135
|
+
const encrypted = localStorage.getItem('tokens');
|
|
136
|
+
return encrypted ? decrypt(encrypted) : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async setTokens(tokens: TokenPair): Promise<void> {
|
|
140
|
+
localStorage.setItem('tokens', encrypt(tokens));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async clearTokens(): Promise<void> {
|
|
144
|
+
localStorage.removeItem('tokens');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const auth = new Auth({
|
|
149
|
+
storage: new EncryptedStorage(),
|
|
150
|
+
refreshEndpoint: '/api/auth/refresh'
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## React Example
|
|
155
|
+
|
|
156
|
+
See [examples/react-integration.ts](examples/react-integration.ts) for full Context API setup.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// AuthProvider.tsx
|
|
160
|
+
const auth = new Auth({
|
|
161
|
+
storage: new WebStorage('localStorage'),
|
|
162
|
+
refreshEndpoint: '/api/auth/refresh'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
client.use(auth.middleware());
|
|
166
|
+
|
|
167
|
+
export function AuthProvider({ children }) {
|
|
168
|
+
const [user, setUser] = useState(null);
|
|
169
|
+
|
|
170
|
+
const login = async (email, password) => {
|
|
171
|
+
await auth.login('/api/login', { email, password });
|
|
172
|
+
const res = await client.get('/me');
|
|
173
|
+
setUser(res.data);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<AuthContext.Provider value={{ user, login }}>
|
|
178
|
+
{children}
|
|
179
|
+
</AuthContext.Provider>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Requirements
|
|
185
|
+
|
|
186
|
+
- Node.js 18+
|
|
187
|
+
- netreq 0.1.0+
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
package/dist/Auth.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Authentication plugin for netreq.
|
|
3
|
+
* Provides automatic token injection, seamless token refresh with queue management,
|
|
4
|
+
* and session state events.
|
|
5
|
+
*/
|
|
6
|
+
import type { AuthPluginOptions, TokenPair, LoginCredentials, LoginResult } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Authentication plugin for netreq HTTP client.
|
|
9
|
+
*
|
|
10
|
+
* This plugin provides:
|
|
11
|
+
* 1. Automatic Authorization header injection
|
|
12
|
+
* 2. Seamless token refresh with mutex/queue pattern
|
|
13
|
+
* 3. Flexible storage adapters
|
|
14
|
+
* 4. Session state events
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createClient } from 'netreq';
|
|
19
|
+
* import { Auth, WebStorage } from '@netreq/auth';
|
|
20
|
+
*
|
|
21
|
+
* const auth = new Auth({
|
|
22
|
+
* storage: new WebStorage('localStorage'),
|
|
23
|
+
* refreshEndpoint: '/auth/refresh',
|
|
24
|
+
* onSessionExpired: () => {
|
|
25
|
+
* window.location.href = '/login';
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* const client = createClient({
|
|
30
|
+
* baseUrl: 'https://api.example.com'
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Apply the plugin
|
|
34
|
+
* client.use(auth.middleware());
|
|
35
|
+
*
|
|
36
|
+
* // Login
|
|
37
|
+
* await auth.login('/auth/login', {
|
|
38
|
+
* email: 'user@example.com',
|
|
39
|
+
* password: 'secret'
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // All subsequent requests automatically include Authorization header
|
|
43
|
+
* const user = await client.get('/me');
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class Auth {
|
|
47
|
+
private readonly options;
|
|
48
|
+
private state;
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new Auth instance.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Plugin configuration options
|
|
53
|
+
*/
|
|
54
|
+
constructor(options: AuthPluginOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Initializes tokens from storage on plugin creation.
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
private initializeFromStorage;
|
|
60
|
+
/**
|
|
61
|
+
* Emits a session event to registered listeners.
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
private emit;
|
|
65
|
+
/**
|
|
66
|
+
* Performs token refresh operation.
|
|
67
|
+
* This method implements the core refresh logic with retry mechanism.
|
|
68
|
+
*
|
|
69
|
+
* Step 1: Check if refresh token exists
|
|
70
|
+
* Step 2: Make refresh request to endpoint
|
|
71
|
+
* Step 3: Extract and store new tokens
|
|
72
|
+
* Step 4: Reset retry counter on success
|
|
73
|
+
* Step 5: Handle failures and retry logic
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
private performRefresh;
|
|
78
|
+
/**
|
|
79
|
+
* Handles refresh failure by clearing state and notifying listeners.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
private handleRefreshFailure;
|
|
83
|
+
/**
|
|
84
|
+
* Processes the refresh queue after successful token refresh.
|
|
85
|
+
* Replays all queued requests with the new access token.
|
|
86
|
+
*
|
|
87
|
+
* Step 1: Get the new access token
|
|
88
|
+
* Step 2: Process each queued request
|
|
89
|
+
* Step 3: Resolve with updated headers or reject on failure
|
|
90
|
+
* Step 4: Clear the queue
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
private processQueue;
|
|
95
|
+
/**
|
|
96
|
+
* Handles 401 Unauthorized responses by initiating token refresh.
|
|
97
|
+
* Implements the mutex pattern to prevent multiple concurrent refresh attempts.
|
|
98
|
+
*
|
|
99
|
+
* Mutex Pattern Explanation:
|
|
100
|
+
* - When a 401 is received, we check if a refresh is already in progress
|
|
101
|
+
* - If yes: Add the request to queue and wait for refresh to complete
|
|
102
|
+
* - If no: Start refresh, queue other requests that arrive during refresh
|
|
103
|
+
* - After refresh: Process all queued requests with new token or reject them
|
|
104
|
+
*
|
|
105
|
+
* This prevents the "race condition" where multiple 401s trigger multiple
|
|
106
|
+
* refresh requests simultaneously.
|
|
107
|
+
*
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
private handleUnauthorized;
|
|
111
|
+
/**
|
|
112
|
+
* Returns the netreq middleware function.
|
|
113
|
+
* This function is passed to client.use() to enable authentication.
|
|
114
|
+
*
|
|
115
|
+
* The middleware:
|
|
116
|
+
* 1. Injects Authorization header if token exists
|
|
117
|
+
* 2. Intercepts 401 responses and triggers token refresh
|
|
118
|
+
* 3. Queues requests during refresh to prevent race conditions
|
|
119
|
+
*
|
|
120
|
+
* @returns Middleware function for netreq client
|
|
121
|
+
*/
|
|
122
|
+
middleware(): {
|
|
123
|
+
/**
|
|
124
|
+
* Request interceptor - adds Authorization header.
|
|
125
|
+
*/
|
|
126
|
+
onRequest(config: {
|
|
127
|
+
path: string;
|
|
128
|
+
method?: string;
|
|
129
|
+
headers?: Record<string, string>;
|
|
130
|
+
body?: unknown;
|
|
131
|
+
}): Promise<typeof config>;
|
|
132
|
+
/**
|
|
133
|
+
* Response interceptor - handles 401 errors and triggers refresh.
|
|
134
|
+
*/
|
|
135
|
+
onResponse(response: {
|
|
136
|
+
status: number;
|
|
137
|
+
statusText: string;
|
|
138
|
+
headers: Record<string, string>;
|
|
139
|
+
data: unknown;
|
|
140
|
+
}, requestConfig: {
|
|
141
|
+
path: string;
|
|
142
|
+
method?: string;
|
|
143
|
+
headers?: Record<string, string>;
|
|
144
|
+
body?: unknown;
|
|
145
|
+
}): Promise<typeof response>;
|
|
146
|
+
/**
|
|
147
|
+
* Error interceptor - handles network errors.
|
|
148
|
+
*/
|
|
149
|
+
onError(error: Error): Promise<Error>;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Logs in a user and stores tokens.
|
|
153
|
+
*
|
|
154
|
+
* @param endpoint - Login API endpoint
|
|
155
|
+
* @param credentials - Login credentials
|
|
156
|
+
* @returns Login result with success status
|
|
157
|
+
*/
|
|
158
|
+
login(endpoint: string, credentials: LoginCredentials): Promise<LoginResult>;
|
|
159
|
+
/**
|
|
160
|
+
* Logs out the current user and clears all tokens.
|
|
161
|
+
*/
|
|
162
|
+
logout(): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Gets the current access token.
|
|
165
|
+
* @returns Current access token or null if not logged in
|
|
166
|
+
*/
|
|
167
|
+
getAccessToken(): string | null;
|
|
168
|
+
/**
|
|
169
|
+
* Checks if user is currently authenticated.
|
|
170
|
+
* @returns True if access token exists
|
|
171
|
+
*/
|
|
172
|
+
isAuthenticated(): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Manually sets tokens (useful for OAuth flows or testing).
|
|
175
|
+
*
|
|
176
|
+
* @param tokens - Token pair to set
|
|
177
|
+
*/
|
|
178
|
+
setTokens(tokens: TokenPair): Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=Auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../src/Auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAGjB,SAAS,EAET,gBAAgB,EAChB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAWpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO,CAQtB;IAEF,OAAO,CAAC,KAAK,CAAY;IAEzB;;;;OAIG;gBACS,OAAO,EAAE,iBAAiB;IA+BtC;;;OAGG;YACW,qBAAqB;IAYnC;;;OAGG;IACH,OAAO,CAAC,IAAI;IAkBZ;;;;;;;;;;;OAWG;YACW,cAAc;IAyD5B;;;OAGG;YACW,oBAAoB;IAgBlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,YAAY;IAsBpB;;;;;;;;;;;;;;OAcG;YACW,kBAAkB;IAyChC;;;;;;;;;;OAUG;IACH,UAAU;QAIN;;WAEG;0BACqB;YACtB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,EAAE,OAAO,CAAC;SAChB,GAAG,OAAO,CAAC,OAAO,MAAM,CAAC;QAiB1B;;WAEG;6BAES;YACR,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAChC,IAAI,EAAE,OAAO,CAAC;SACf,iBACc;YACb,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,EAAE,OAAO,CAAC;SAChB,GACA,OAAO,CAAC,OAAO,QAAQ,CAAC;QAsC3B;;WAEG;uBACkB,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;;IAM/C;;;;;;OAMG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;IA6ClF;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAc7B;;;OAGG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;;OAGG;IACH,eAAe,IAAI,OAAO;IAI1B;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAKlD"}
|