@authgate/browser 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Lupatsiy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,18 +2,33 @@
2
2
 
3
3
  Minimal browser-side helpers for applications using **AuthGate**.
4
4
 
5
- This package provides small, explicit utilities to integrate browser-based UIs
6
- with an AuthGate-backed authentication flow. It intentionally avoids framework
7
- coupling and hidden behavior.
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
- ## Features
13
+ ## Design goals
12
14
 
13
- - Read AuthGate CSRF token from cookies
14
- - Perform a safe logout request with CSRF protection
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
15
19
  - Zero dependencies
16
- - Framework-agnostic (works with React, Vue, vanilla JS, etc.)
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
17
32
 
18
33
  ---
19
34
 
@@ -37,6 +52,9 @@ const csrf = getCSRFToken();
37
52
 
38
53
  Returns the value of the `authgate_csrf` cookie, or `null` if not present.
39
54
 
55
+ This function only **reads** the CSRF token.
56
+ It does not generate or validate it.
57
+
40
58
  ---
41
59
 
42
60
  ### Logout
@@ -44,7 +62,7 @@ Returns the value of the `authgate_csrf` cookie, or `null` if not present.
44
62
  ```ts
45
63
  import { logout } from "@authgate/browser";
46
64
 
47
- await logout();
65
+ const result = await logout();
48
66
  ```
49
67
 
50
68
  This will:
@@ -53,6 +71,17 @@ This will:
53
71
  - Attach the CSRF token via `X-CSRF-Token`
54
72
  - Include credentials (`cookies`)
55
73
 
74
+ The function returns an explicit result:
75
+
76
+ ```ts
77
+ type LogoutResult =
78
+ | { ok: true }
79
+ | { ok: false; reason: "missing_csrf" | "request_failed" | "unauthorized" };
80
+ ```
81
+
82
+ Applications that do not need to react programmatically may safely ignore the
83
+ return value.
84
+
56
85
  ---
57
86
 
58
87
  ### Logout with redirect
@@ -61,41 +90,119 @@ This will:
61
90
  await logout({ redirectTo: "/" });
62
91
  ```
63
92
 
64
- After a successful logout request, the browser is redirected to the given path.
93
+ If the logout request succeeds, the browser is redirected to the given path.
94
+
95
+ Redirecting is an optional side-effect and does **not** define success.
96
+
97
+ ---
98
+
99
+ ## Session refresh (explicit)
100
+
101
+ ### `refreshSession`
102
+
103
+ ```ts
104
+ import { refreshSession } from "@authgate/browser";
105
+
106
+ const refreshed = await refreshSession();
107
+ ```
108
+
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.
65
128
 
66
129
  ---
67
130
 
68
- ## Security Model
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) {
170
+ setUser(null);
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Security model
69
177
 
70
178
  - CSRF tokens are **not generated** by this package
71
179
  - CSRF validation is **enforced by AuthGate**
72
- - This package only forwards existing CSRF state
180
+ - Refresh tokens are **never exposed to JavaScript**
181
+ - All authentication state is owned by AuthGate
73
182
 
74
- No cookies are set, modified, or cleared by this library.
183
+ This package only forwards existing browser state explicitly.
75
184
 
76
185
  ---
77
186
 
78
- ## What This Package Does NOT Do
187
+ ## What this package does NOT do
79
188
 
80
189
  - No authentication logic
81
- - No token refresh
190
+ - No credential storage
191
+ - No background token refresh
82
192
  - No session management
83
- - No redirects except when explicitly requested
193
+ - No authorization or role handling
194
+ - No implicit redirects
84
195
  - No framework-specific helpers
85
196
 
86
- 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.
87
199
 
88
200
  ---
89
201
 
90
202
  ## Compatibility
91
203
 
92
204
  - Works with any backend protected by AuthGate
93
- - Compatible with SSR and SPA architectures
94
- - Safe to use in multi-app or monorepo setups
95
205
 
96
206
  ---
97
207
 
98
208
  ## License
99
-
100
- MIT
101
-
package/dist/index.d.ts CHANGED
@@ -1,4 +1,64 @@
1
+ /**
2
+ * Returns the AuthGate CSRF token from the browser cookies.
3
+ *
4
+ * The CSRF token is issued by AuthGate and stored in the `authgate_csrf`
5
+ * cookie. This helper does not validate the token; it only reads it.
6
+ *
7
+ * @returns The CSRF token string, or `null` if the cookie is missing.
8
+ */
1
9
  export declare function getCSRFToken(): string | null;
10
+ /**
11
+ * The result of a logout attempt.
12
+ *
13
+ * - `{ ok: true }` indicates that logout succeeded.
14
+ * - `{ ok: false, reason }` indicates that logout failed for a known reason.
15
+ */
16
+ type LogoutResult = {
17
+ ok: true;
18
+ } | {
19
+ ok: false;
20
+ reason: "missing_csrf" | "request_failed" | "unauthorized";
21
+ };
22
+ /**
23
+ * Logs the user out by calling the AuthGate logout endpoint.
24
+ *
25
+ * This function:
26
+ * - Reads the CSRF token from the browser cookies
27
+ * - Sends a POST request to `/auth/logout`
28
+ * - Optionally redirects the browser on success
29
+ *
30
+ * Redirecting is a side-effect and does not define success. Applications
31
+ * that need to react to logout programmatically (e.g. SPA state updates)
32
+ * should inspect the returned result instead.
33
+ *
34
+ * @param opts.redirectTo Optional URL to redirect to after successful logout.
35
+ * @returns A `LogoutResult` indicating whether logout succeeded or failed.
36
+ */
2
37
  export declare function logout(opts?: {
3
38
  redirectTo?: string;
4
- }): Promise<void>;
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>;
64
+ export {};
package/dist/index.js CHANGED
@@ -1,22 +1,128 @@
1
+ /**
2
+ * Reads a cookie value by name from `document.cookie`.
3
+ *
4
+ * This is a small internal helper used by the AuthGate browser SDK.
5
+ * It returns `null` if the cookie is not present.
6
+ */
1
7
  function getCookie(name) {
2
8
  const match = document.cookie
3
9
  .split("; ")
4
10
  .find((c) => c.startsWith(name + "="));
5
11
  return match ? decodeURIComponent(match.split("=")[1]) : null;
6
12
  }
13
+ /**
14
+ * Returns the AuthGate CSRF token from the browser cookies.
15
+ *
16
+ * The CSRF token is issued by AuthGate and stored in the `authgate_csrf`
17
+ * cookie. This helper does not validate the token; it only reads it.
18
+ *
19
+ * @returns The CSRF token string, or `null` if the cookie is missing.
20
+ */
7
21
  export function getCSRFToken() {
8
22
  return getCookie("authgate_csrf");
9
23
  }
24
+ /**
25
+ * Logs the user out by calling the AuthGate logout endpoint.
26
+ *
27
+ * This function:
28
+ * - Reads the CSRF token from the browser cookies
29
+ * - Sends a POST request to `/auth/logout`
30
+ * - Optionally redirects the browser on success
31
+ *
32
+ * Redirecting is a side-effect and does not define success. Applications
33
+ * that need to react to logout programmatically (e.g. SPA state updates)
34
+ * should inspect the returned result instead.
35
+ *
36
+ * @param opts.redirectTo Optional URL to redirect to after successful logout.
37
+ * @returns A `LogoutResult` indicating whether logout succeeded or failed.
38
+ */
10
39
  export async function logout(opts) {
11
40
  const csrf = getCSRFToken();
12
- await fetch("/auth/logout", {
13
- method: "POST",
14
- headers: {
15
- "X-CSRF-Token": csrf ?? "",
16
- },
17
- credentials: "include",
18
- });
19
- if (opts?.redirectTo !== undefined) {
41
+ if (!csrf) {
42
+ return { ok: false, reason: "missing_csrf" };
43
+ }
44
+ let res;
45
+ try {
46
+ res = await fetch("/auth/logout", {
47
+ method: "POST",
48
+ headers: {
49
+ "X-CSRF-Token": csrf,
50
+ },
51
+ credentials: "include",
52
+ });
53
+ }
54
+ catch {
55
+ return { ok: false, reason: "request_failed" };
56
+ }
57
+ if (!res.ok) {
58
+ return {
59
+ ok: false,
60
+ reason: res.status === 401 || res.status === 403
61
+ ? "unauthorized"
62
+ : "request_failed",
63
+ };
64
+ }
65
+ if (opts?.redirectTo) {
20
66
  window.location.href = opts.redirectTo;
21
67
  }
68
+ return { ok: true };
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;
22
128
  }
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@authgate/browser",
3
- "version": "0.1.0",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "https://github.com/alexlup06-authgate/authgate-browser.git"
6
+ },
7
+ "version": "0.3.0",
4
8
  "description": "Browser-side helpers for AuthGate (logout, CSRF forwarding)",
5
9
  "license": "MIT",
6
10
  "type": "module",