@authgate/browser 0.2.0 → 0.3.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 +105 -29
- package/dist/index.d.ts +24 -0
- package/dist/index.js +59 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,20 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Minimal browser-side helpers for applications using **AuthGate**.
|
|
4
4
|
|
|
5
|
-
This package provides
|
|
6
|
-
with an AuthGate-backed authentication
|
|
7
|
-
|
|
5
|
+
This package provides **explicit, framework-agnostic primitives** for integrating
|
|
6
|
+
browser-based UIs (SSR or SPA) with an AuthGate-backed authentication system.
|
|
7
|
+
|
|
8
|
+
It intentionally avoids hidden behavior, background state mutation, or
|
|
9
|
+
framework-specific abstractions.
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Design goals
|
|
12
14
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- Explicit behavior (no magic, no background auth)
|
|
16
|
+
- Browser-only responsibility (cookies, CSRF, refresh)
|
|
17
|
+
- Framework-agnostic (React, Vue, Svelte, vanilla JS)
|
|
18
|
+
- Composable primitives + optional convenience helpers
|
|
17
19
|
- Zero dependencies
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Read AuthGate CSRF token from browser cookies
|
|
26
|
+
- Perform a CSRF-protected logout request
|
|
27
|
+
- Explicit browser-side session refresh
|
|
28
|
+
- Optional `fetch` wrapper with **single-retry refresh semantics**
|
|
29
|
+
- Clear success / failure signaling
|
|
30
|
+
- Optional redirect on logout
|
|
31
|
+
- No runtime dependencies
|
|
19
32
|
|
|
20
33
|
---
|
|
21
34
|
|
|
@@ -39,7 +52,8 @@ const csrf = getCSRFToken();
|
|
|
39
52
|
|
|
40
53
|
Returns the value of the `authgate_csrf` cookie, or `null` if not present.
|
|
41
54
|
|
|
42
|
-
This function only **reads** the CSRF token.
|
|
55
|
+
This function only **reads** the CSRF token.
|
|
56
|
+
It does not generate or validate it.
|
|
43
57
|
|
|
44
58
|
---
|
|
45
59
|
|
|
@@ -57,7 +71,7 @@ This will:
|
|
|
57
71
|
- Attach the CSRF token via `X-CSRF-Token`
|
|
58
72
|
- Include credentials (`cookies`)
|
|
59
73
|
|
|
60
|
-
The function returns
|
|
74
|
+
The function returns an explicit result:
|
|
61
75
|
|
|
62
76
|
```ts
|
|
63
77
|
type LogoutResult =
|
|
@@ -65,8 +79,8 @@ type LogoutResult =
|
|
|
65
79
|
| { ok: false; reason: "missing_csrf" | "request_failed" | "unauthorized" };
|
|
66
80
|
```
|
|
67
81
|
|
|
68
|
-
Applications that do not need to react programmatically
|
|
69
|
-
|
|
82
|
+
Applications that do not need to react programmatically may safely ignore the
|
|
83
|
+
return value.
|
|
70
84
|
|
|
71
85
|
---
|
|
72
86
|
|
|
@@ -79,54 +93,116 @@ await logout({ redirectTo: "/" });
|
|
|
79
93
|
If the logout request succeeds, the browser is redirected to the given path.
|
|
80
94
|
|
|
81
95
|
Redirecting is an optional side-effect and does **not** define success.
|
|
82
|
-
Applications may choose to handle navigation themselves instead.
|
|
83
96
|
|
|
84
97
|
---
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
## Session refresh (explicit)
|
|
100
|
+
|
|
101
|
+
### `refreshSession`
|
|
87
102
|
|
|
88
103
|
```ts
|
|
89
|
-
|
|
104
|
+
import { refreshSession } from "@authgate/browser";
|
|
105
|
+
|
|
106
|
+
const refreshed = await refreshSession();
|
|
107
|
+
```
|
|
90
108
|
|
|
91
|
-
|
|
109
|
+
Attempts to refresh the current AuthGate session by calling:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
POST /auth/refresh
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Behavior:
|
|
116
|
+
|
|
117
|
+
- Returns `true` if refresh succeeded
|
|
118
|
+
- Returns `false` if refresh failed for any reason
|
|
119
|
+
|
|
120
|
+
This function:
|
|
121
|
+
|
|
122
|
+
- does **not** retry
|
|
123
|
+
- does **not** redirect
|
|
124
|
+
- does **not** throw
|
|
125
|
+
- does **not** modify application state
|
|
126
|
+
|
|
127
|
+
It is intended for applications that want **manual control** over refresh logic.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Fetch wrapper (optional convenience)
|
|
132
|
+
|
|
133
|
+
### `authFetch`
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { authFetch } from "@authgate/browser";
|
|
137
|
+
|
|
138
|
+
const res = await authFetch("/api/data");
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`authFetch` is an **optional convenience wrapper** around `fetch` with
|
|
142
|
+
AuthGate-aware refresh behavior.
|
|
143
|
+
|
|
144
|
+
Behavior:
|
|
145
|
+
|
|
146
|
+
1. Performs the request with credentials
|
|
147
|
+
2. If the response is **not `401`**, returns it directly
|
|
148
|
+
3. If the response **is `401`**:
|
|
149
|
+
- Attempts `refreshSession()`
|
|
150
|
+
- If refresh succeeds, retries the original request **once**
|
|
151
|
+
- Otherwise, returns the original `401` response
|
|
152
|
+
|
|
153
|
+
Important properties:
|
|
154
|
+
|
|
155
|
+
- At most **one retry**
|
|
156
|
+
- No redirects
|
|
157
|
+
- No background refresh
|
|
158
|
+
- No swallowed failures
|
|
159
|
+
|
|
160
|
+
Applications remain fully in control of UX decisions.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Example (React / SPA)
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const res = await authFetch("/api/me");
|
|
168
|
+
|
|
169
|
+
if (res.status === 401) {
|
|
92
170
|
setUser(null);
|
|
93
|
-
} else {
|
|
94
|
-
console.error("Logout failed:", result.reason);
|
|
95
171
|
}
|
|
96
172
|
```
|
|
97
173
|
|
|
98
174
|
---
|
|
99
175
|
|
|
100
|
-
## Security
|
|
176
|
+
## Security model
|
|
101
177
|
|
|
102
178
|
- CSRF tokens are **not generated** by this package
|
|
103
179
|
- CSRF validation is **enforced by AuthGate**
|
|
104
|
-
-
|
|
180
|
+
- Refresh tokens are **never exposed to JavaScript**
|
|
181
|
+
- All authentication state is owned by AuthGate
|
|
105
182
|
|
|
106
|
-
|
|
183
|
+
This package only forwards existing browser state explicitly.
|
|
107
184
|
|
|
108
185
|
---
|
|
109
186
|
|
|
110
|
-
## What
|
|
187
|
+
## What this package does NOT do
|
|
111
188
|
|
|
112
189
|
- No authentication logic
|
|
113
|
-
- No
|
|
190
|
+
- No credential storage
|
|
191
|
+
- No background token refresh
|
|
114
192
|
- No session management
|
|
193
|
+
- No authorization or role handling
|
|
115
194
|
- No implicit redirects
|
|
116
195
|
- No framework-specific helpers
|
|
117
196
|
|
|
118
|
-
This package exists solely to reduce boilerplate and prevent integration mistakes
|
|
197
|
+
This package exists solely to reduce boilerplate and prevent integration mistakes
|
|
198
|
+
while preserving full application control.
|
|
119
199
|
|
|
120
200
|
---
|
|
121
201
|
|
|
122
202
|
## Compatibility
|
|
123
203
|
|
|
124
204
|
- Works with any backend protected by AuthGate
|
|
125
|
-
- Compatible with SSR and SPA architectures
|
|
126
205
|
|
|
127
206
|
---
|
|
128
207
|
|
|
129
208
|
## License
|
|
130
|
-
|
|
131
|
-
MIT
|
|
132
|
-
|
package/dist/index.d.ts
CHANGED
|
@@ -37,4 +37,28 @@ type LogoutResult = {
|
|
|
37
37
|
export declare function logout(opts?: {
|
|
38
38
|
redirectTo?: string;
|
|
39
39
|
}): Promise<LogoutResult>;
|
|
40
|
+
/**
|
|
41
|
+
* authFetch performs a fetch request with AuthGate-aware refresh-once behavior.
|
|
42
|
+
*
|
|
43
|
+
* Behavior:
|
|
44
|
+
* - Always includes credentials
|
|
45
|
+
* - If the response is NOT 401 - returns it directly
|
|
46
|
+
* - If the response IS 401:
|
|
47
|
+
* - Attempts POST /auth/refresh with CSRF
|
|
48
|
+
* - If refresh succeeds - retries original request ONCE
|
|
49
|
+
* - Otherwise - returns original 401 response
|
|
50
|
+
*/
|
|
51
|
+
export declare function authFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
52
|
+
/**
|
|
53
|
+
* refreshSession attempts to refresh the current AuthGate session.
|
|
54
|
+
*
|
|
55
|
+
* It performs:
|
|
56
|
+
* POST /auth/refresh
|
|
57
|
+
* with CSRF protection and credentials
|
|
58
|
+
*
|
|
59
|
+
* The function:
|
|
60
|
+
* - returns true if refresh succeeded
|
|
61
|
+
* - returns false if refresh failed for any reason
|
|
62
|
+
*/
|
|
63
|
+
export declare function refreshSession(): Promise<boolean>;
|
|
40
64
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -67,3 +67,62 @@ export async function logout(opts) {
|
|
|
67
67
|
}
|
|
68
68
|
return { ok: true };
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* authFetch performs a fetch request with AuthGate-aware refresh-once behavior.
|
|
72
|
+
*
|
|
73
|
+
* Behavior:
|
|
74
|
+
* - Always includes credentials
|
|
75
|
+
* - If the response is NOT 401 - returns it directly
|
|
76
|
+
* - If the response IS 401:
|
|
77
|
+
* - Attempts POST /auth/refresh with CSRF
|
|
78
|
+
* - If refresh succeeds - retries original request ONCE
|
|
79
|
+
* - Otherwise - returns original 401 response
|
|
80
|
+
*/
|
|
81
|
+
export async function authFetch(input, init = {}) {
|
|
82
|
+
const res = await fetch(input, withCredentials(init));
|
|
83
|
+
if (res.status !== 401) {
|
|
84
|
+
return res;
|
|
85
|
+
}
|
|
86
|
+
const refreshed = await refreshSession();
|
|
87
|
+
if (!refreshed) {
|
|
88
|
+
return res;
|
|
89
|
+
}
|
|
90
|
+
return fetch(input, withCredentials(init));
|
|
91
|
+
}
|
|
92
|
+
function withCredentials(init) {
|
|
93
|
+
return {
|
|
94
|
+
...init,
|
|
95
|
+
credentials: "include",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* refreshSession attempts to refresh the current AuthGate session.
|
|
100
|
+
*
|
|
101
|
+
* It performs:
|
|
102
|
+
* POST /auth/refresh
|
|
103
|
+
* with CSRF protection and credentials
|
|
104
|
+
*
|
|
105
|
+
* The function:
|
|
106
|
+
* - returns true if refresh succeeded
|
|
107
|
+
* - returns false if refresh failed for any reason
|
|
108
|
+
*/
|
|
109
|
+
export async function refreshSession() {
|
|
110
|
+
const csrf = getCSRFToken();
|
|
111
|
+
if (!csrf) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
let res;
|
|
115
|
+
try {
|
|
116
|
+
res = await fetch("/auth/refresh", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"X-CSRF-Token": csrf,
|
|
120
|
+
},
|
|
121
|
+
credentials: "include",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return res.ok;
|
|
128
|
+
}
|
package/package.json
CHANGED