@nexauthxyz/browser 0.1.2

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.
@@ -0,0 +1,95 @@
1
+ import { NexAuthError } from './errors.js';
2
+ function normalizeKeyPart(value, name) {
3
+ const normalized = value.trim();
4
+ if (!normalized) {
5
+ throw new NexAuthError('invalid_configuration', `${name} cannot be empty.`);
6
+ }
7
+ return normalized.replace(/[^a-zA-Z0-9._-]/g, '_');
8
+ }
9
+ export function getDefaultBrowserStorage() {
10
+ if (typeof window === 'undefined' ||
11
+ typeof window.sessionStorage === 'undefined') {
12
+ throw new NexAuthError('storage_error', 'Browser session storage is unavailable.');
13
+ }
14
+ return window.sessionStorage;
15
+ }
16
+ export class NexAuthStorageManager {
17
+ storage;
18
+ namespace;
19
+ constructor(storage, prefix, clientId) {
20
+ this.storage = storage;
21
+ const normalizedPrefix = normalizeKeyPart(prefix, 'Storage prefix');
22
+ const normalizedClientId = normalizeKeyPart(clientId, 'Client ID');
23
+ this.namespace = `${normalizedPrefix}:${normalizedClientId}`;
24
+ }
25
+ get(entry) {
26
+ const key = this.getKey(entry);
27
+ let raw;
28
+ try {
29
+ raw = this.storage.getItem(key);
30
+ }
31
+ catch (error) {
32
+ throw new NexAuthError('storage_error', 'Unable to read NexAuth browser storage.', {
33
+ cause: error,
34
+ });
35
+ }
36
+ if (raw === null) {
37
+ return null;
38
+ }
39
+ try {
40
+ return JSON.parse(raw);
41
+ }
42
+ catch (error) {
43
+ /*
44
+ * Corrupted browser state must not repeatedly break authentication.
45
+ */
46
+ this.remove(entry);
47
+ throw new NexAuthError('storage_error', 'Stored NexAuth browser state is invalid.', {
48
+ cause: error,
49
+ });
50
+ }
51
+ }
52
+ set(entry, value) {
53
+ const key = this.getKey(entry);
54
+ let serialized;
55
+ try {
56
+ serialized = JSON.stringify(value);
57
+ }
58
+ catch (error) {
59
+ throw new NexAuthError('storage_error', 'Unable to serialize NexAuth browser state.', {
60
+ cause: error,
61
+ });
62
+ }
63
+ try {
64
+ this.storage.setItem(key, serialized);
65
+ }
66
+ catch (error) {
67
+ throw new NexAuthError('storage_error', 'Unable to save NexAuth browser state.', {
68
+ cause: error,
69
+ });
70
+ }
71
+ }
72
+ remove(entry) {
73
+ try {
74
+ this.storage.removeItem(this.getKey(entry));
75
+ }
76
+ catch (error) {
77
+ throw new NexAuthError('storage_error', 'Unable to remove NexAuth browser state.', {
78
+ cause: error,
79
+ });
80
+ }
81
+ }
82
+ clear() {
83
+ /*
84
+ * Remove only this NexAuth client's entries.
85
+ * Never call storage.clear(), because it would delete unrelated
86
+ * application data.
87
+ */
88
+ this.remove('session');
89
+ this.remove('transaction');
90
+ }
91
+ getKey(entry) {
92
+ return `${this.namespace}:${entry}`;
93
+ }
94
+ }
95
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ export interface OpenIdConfiguration {
2
+ readonly issuer: string;
3
+ readonly authorization_endpoint: string;
4
+ readonly token_endpoint: string;
5
+ readonly userinfo_endpoint: string;
6
+ readonly jwks_uri: string;
7
+ readonly end_session_endpoint?: string;
8
+ readonly response_types_supported: readonly string[];
9
+ readonly grant_types_supported: readonly string[];
10
+ readonly subject_types_supported: readonly string[];
11
+ readonly id_token_signing_alg_values_supported: readonly string[];
12
+ readonly scopes_supported: readonly string[];
13
+ readonly claims_supported?: readonly string[];
14
+ readonly code_challenge_methods_supported: readonly string[];
15
+ readonly token_endpoint_auth_methods_supported: readonly string[];
16
+ }
17
+ export interface NexAuthUser {
18
+ readonly sub: string;
19
+ readonly email?: string;
20
+ readonly email_verified?: boolean;
21
+ readonly name?: string;
22
+ readonly picture?: string;
23
+ readonly organization_id?: string;
24
+ readonly organization_role?: string;
25
+ readonly permissions?: readonly string[];
26
+ readonly [claim: string]: unknown;
27
+ }
28
+ export interface OAuthTokenResponse {
29
+ readonly access_token: string;
30
+ readonly token_type: string;
31
+ readonly expires_in: number;
32
+ readonly scope?: string;
33
+ readonly id_token?: string;
34
+ readonly refresh_token?: string;
35
+ }
36
+ export interface NexAuthIdTokenClaims {
37
+ readonly iss: string;
38
+ readonly sub: string;
39
+ readonly aud: string | readonly string[];
40
+ readonly exp: number;
41
+ readonly iat: number;
42
+ readonly nonce: string;
43
+ readonly azp?: string;
44
+ readonly email?: string;
45
+ readonly email_verified?: boolean;
46
+ readonly name?: string;
47
+ readonly picture?: string;
48
+ readonly [claim: string]: unknown;
49
+ }
50
+ export interface NexAuthSession {
51
+ readonly accessToken: string;
52
+ readonly tokenType: 'Bearer';
53
+ readonly expiresAt: number;
54
+ readonly scope?: string;
55
+ readonly idToken?: string;
56
+ readonly refreshToken?: string;
57
+ readonly user: NexAuthUser;
58
+ readonly idTokenClaims?: NexAuthIdTokenClaims;
59
+ }
60
+ export interface AuthorizationTransaction {
61
+ readonly state: string;
62
+ readonly nonce: string;
63
+ readonly codeVerifier: string;
64
+ readonly redirectUri: string;
65
+ readonly returnTo?: string;
66
+ readonly createdAt: number;
67
+ }
68
+ export interface NexAuthStorage {
69
+ getItem(key: string): string | null;
70
+ setItem(key: string, value: string): void;
71
+ removeItem(key: string): void;
72
+ }
73
+ export interface NexAuthBrowserClientOptions {
74
+ /**
75
+ * NexAuth issuer. It must exactly match the issuer returned by discovery.
76
+ *
77
+ * Example: https://api.nexauth.xyz
78
+ */
79
+ readonly issuer: string;
80
+ /**
81
+ * Public application client ID generated by NexAuth.
82
+ */
83
+ readonly clientId: string;
84
+ /**
85
+ * Registered callback URI.
86
+ *
87
+ * Defaults to `${window.location.origin}/auth/callback`.
88
+ */
89
+ readonly redirectUri?: string;
90
+ /**
91
+ * Requested OIDC scopes.
92
+ *
93
+ * Defaults to `openid profile email`.
94
+ */
95
+ readonly scope?: string;
96
+ /**
97
+ * Optional storage implementation.
98
+ *
99
+ * Defaults to browser sessionStorage.
100
+ */
101
+ readonly storage?: NexAuthStorage;
102
+ /**
103
+ * Optional fetch implementation, primarily for testing.
104
+ */
105
+ readonly fetch?: typeof globalThis.fetch;
106
+ /**
107
+ * Storage-key namespace.
108
+ *
109
+ * Defaults to `nexauth`.
110
+ */
111
+ readonly storageKeyPrefix?: string;
112
+ }
113
+ export interface LoginOptions {
114
+ /**
115
+ * Application route restored after callback processing.
116
+ *
117
+ * This is stored locally and is never accepted as a server redirect URI.
118
+ */
119
+ readonly returnTo?: string;
120
+ /**
121
+ * Optional OIDC prompt value.
122
+ */
123
+ readonly prompt?: 'none' | 'login' | 'consent' | 'select_account';
124
+ /**
125
+ * Optional additional scopes for this authorization request.
126
+ */
127
+ readonly additionalScopes?: readonly string[];
128
+ }
129
+ export interface AuthorizationCallbackInput {
130
+ readonly url?: string;
131
+ }
132
+ export interface AuthorizationCallbackResult {
133
+ readonly session: NexAuthSession;
134
+ readonly returnTo?: string;
135
+ }
136
+ export interface LogoutOptions {
137
+ /**
138
+ * If true, only local SDK state is cleared.
139
+ */
140
+ readonly localOnly?: boolean;
141
+ }
142
+ export type AuthenticationStatus = 'loading' | 'authenticated' | 'unauthenticated' | 'error';
143
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@nexauthxyz/browser",
3
+ "version": "0.1.2",
4
+ "description": "Official browser SDK for NexAuth using OpenID Connect Authorization Code with PKCE.",
5
+ "author": {
6
+ "name": "nexauthteam",
7
+ "url": "https://nexauth.xyz"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://nexauth.xyz",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/danialafridi1/nexauth.git",
14
+ "directory": "packages/browser-sdk"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/danialafridi1/nexauth/issues"
18
+ },
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "sideEffects": false,
36
+ "scripts": {
37
+ "clean": "rimraf dist",
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit -p tsconfig.json",
40
+ "test": "vitest run --config vitest.config.ts",
41
+ "test:watch": "vitest --config vitest.config.ts",
42
+ "prepack": "npm run clean && npm run build",
43
+ "prepublishOnly": "npm run typecheck && npm run test"
44
+ },
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "keywords": [
49
+ "nexauth",
50
+ "authentication",
51
+ "authorization",
52
+ "oauth2",
53
+ "oidc",
54
+ "openid-connect",
55
+ "pkce",
56
+ "browser",
57
+ "typescript",
58
+ "javascript"
59
+ ],
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "dependencies": {
64
+ "jose": "^6.2.12"
65
+ }
66
+ }
package/readme.md ADDED
@@ -0,0 +1,282 @@
1
+ # `@nexauthxyz/browser`
2
+
3
+ Official framework-independent browser SDK for NexAuth. It implements OpenID Connect Authorization Code Flow with PKCE and can be used by React, Vue, Svelte, Angular, or plain browser applications.
4
+
5
+ ## What this package does
6
+
7
+ - Loads NexAuth endpoints from OIDC discovery.
8
+ - Generates cryptographically secure PKCE, `state`, and `nonce` values.
9
+ - Redirects the browser to the NexAuth authorization endpoint.
10
+ - Exchanges an authorization code for tokens.
11
+ - Validates the returned ID token using the provider JWKS.
12
+ - Validates issuer, audience, signature, expiry, algorithm, and nonce.
13
+ - Loads the signed-in user from the UserInfo endpoint.
14
+ - Stores the authorization transaction and authenticated session in browser `sessionStorage` by default.
15
+ - Clears local authentication state and supports provider logout when advertised by discovery.
16
+
17
+ The SDK does not contain administrative credentials, a client secret, signing keys, or subscription enforcement logic.
18
+
19
+ ## Requirements
20
+
21
+ - A modern browser with Web Crypto, `fetch`, `URL`, and Web Storage support.
22
+ - A NexAuth application with an active subscription/entitlement.
23
+ - A public NexAuth client ID.
24
+ - An exact registered redirect URI.
25
+ - A registered browser origin when required by the NexAuth server.
26
+ - NexAuth discovery, token, UserInfo, and JWKS endpoints available through HTTPS in production.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @nexauthxyz/browser
32
+ ```
33
+
34
+ ## Dependencies
35
+
36
+ | Package | Relationship | Purpose |
37
+ | ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- |
38
+ | `jose` | Runtime dependency | Validates RS256 ID tokens and resolves keys from JWKS. |
39
+ | NexAuth OIDC server | External service dependency | Performs authorization, token issuance, UserInfo, logout, entitlement checks, and application validation. |
40
+
41
+ This package has no dependency on React or another UI framework.
42
+
43
+ ## Basic usage
44
+
45
+ ```ts
46
+ import { NexAuthBrowserClient } from '@nexauthxyz/browser';
47
+
48
+ const nexAuth = new NexAuthBrowserClient({
49
+ issuer: 'https://api.nexauth.xyz',
50
+ clientId: 'nxa_your_public_client_id',
51
+ redirectUri: `${window.location.origin}/auth/callback`,
52
+ scope: 'openid profile email',
53
+ });
54
+ ```
55
+
56
+ The issuer must exactly match the `issuer` returned by:
57
+
58
+ ```text
59
+ https://api.nexauth.xyz/.well-known/openid-configuration
60
+ ```
61
+
62
+ Do not use `https://auth.nexauth.xyz` as the issuer unless that value is also the discovery document issuer.
63
+
64
+ ## Start login
65
+
66
+ ```ts
67
+ await nexAuth.login({
68
+ returnTo: '/dashboard',
69
+ });
70
+ ```
71
+
72
+ The SDK creates and stores PKCE and anti-forgery values before redirecting the browser. `returnTo` is local application state; it is not sent as an OAuth redirect URI.
73
+
74
+ Optional login values:
75
+
76
+ ```ts
77
+ await nexAuth.login({
78
+ returnTo: '/orders',
79
+ prompt: 'login',
80
+ additionalScopes: ['custom.read'],
81
+ });
82
+ ```
83
+
84
+ ## Handle the callback
85
+
86
+ Register the exact callback URI in NexAuth, for example:
87
+
88
+ ```text
89
+ https://shop.example.com/auth/callback
90
+ ```
91
+
92
+ Then process the callback:
93
+
94
+ ```ts
95
+ const result = await nexAuth.handleCallback();
96
+
97
+ console.log(result.session.user);
98
+ console.log(result.returnTo);
99
+
100
+ window.history.replaceState({}, '', result.returnTo ?? '/');
101
+ ```
102
+
103
+ You may supply a URL explicitly for testing or custom routing:
104
+
105
+ ```ts
106
+ const result = await nexAuth.handleCallback({
107
+ url: window.location.href,
108
+ });
109
+ ```
110
+
111
+ ## Read the current session
112
+
113
+ ```ts
114
+ const session = nexAuth.getSession();
115
+
116
+ if (session) {
117
+ console.log(session.user);
118
+ console.log(session.accessToken);
119
+ console.log(new Date(session.expiresAt));
120
+ }
121
+ ```
122
+
123
+ An expired session is not returned as authenticated.
124
+
125
+ ## Call a protected API
126
+
127
+ ```ts
128
+ const session = nexAuth.getSession();
129
+
130
+ if (!session) {
131
+ throw new Error('Authentication is required.');
132
+ }
133
+
134
+ const response = await fetch('https://api.example.com/orders', {
135
+ headers: {
136
+ Authorization: `${session.tokenType} ${session.accessToken}`,
137
+ },
138
+ });
139
+ ```
140
+
141
+ The resource server must validate the access token signature, issuer, audience/client, expiry, and required permissions. A browser session alone is not authorization for a protected backend resource.
142
+
143
+ ## Logout
144
+
145
+ ```ts
146
+ await nexAuth.logout();
147
+ ```
148
+
149
+ Local-only logout:
150
+
151
+ ```ts
152
+ await nexAuth.logout({ localOnly: true });
153
+ ```
154
+
155
+ You can also synchronously remove local SDK state:
156
+
157
+ ```ts
158
+ nexAuth.clearLocalSession();
159
+ ```
160
+
161
+ ## Configuration
162
+
163
+ | Option | Required | Default | Description |
164
+ | ------------------ | -------- | ------------------------ | ------------------------------------------------------------------- |
165
+ | `issuer` | Yes | — | NexAuth OIDC issuer, without a trailing slash. |
166
+ | `clientId` | Yes | — | Public application identifier issued by NexAuth. |
167
+ | `redirectUri` | No | `<origin>/auth/callback` | Exact registered callback URI. |
168
+ | `scope` | No | `openid profile email` | Space-separated requested scopes. |
169
+ | `storage` | No | `window.sessionStorage` | Custom storage implementing `getItem`, `setItem`, and `removeItem`. |
170
+ | `fetch` | No | `globalThis.fetch` | Custom fetch implementation, mainly useful for testing. |
171
+ | `storageKeyPrefix` | No | `nexauth` | Namespace used for SDK storage keys. |
172
+
173
+ ## Error handling
174
+
175
+ ```ts
176
+ import { isNexAuthError } from '@nexauthxyz/browser';
177
+
178
+ try {
179
+ await nexAuth.handleCallback();
180
+ } catch (error: unknown) {
181
+ if (isNexAuthError(error)) {
182
+ console.error(error.code, error.message);
183
+ console.error('Support reference:', error.requestId);
184
+ }
185
+ }
186
+ ```
187
+
188
+ Important error codes include:
189
+
190
+ - `invalid_configuration`
191
+ - `discovery_failed`
192
+ - `invalid_discovery_document`
193
+ - `login_failed`
194
+ - `authorization_error`
195
+ - `invalid_state`
196
+ - `missing_authorization_code`
197
+ - `missing_pkce_transaction`
198
+ - `token_exchange_failed`
199
+ - `invalid_token_response`
200
+ - `id_token_validation_failed`
201
+ - `userinfo_failed`
202
+ - `logout_failed`
203
+ - `network_error`
204
+ - `storage_error`
205
+
206
+ Display `requestId` as a support reference where available. NexAuth administrators can use it to locate the matching server audit event.
207
+
208
+ ## Security model
209
+
210
+ `clientId`, issuer, redirect URI, discovery metadata, and JWKS are public information. Never place these values in frontend code:
211
+
212
+ - Client secrets
213
+ - JWT private keys
214
+ - Database or Redis credentials
215
+ - Administrative API tokens
216
+ - Payment-provider secrets
217
+ - Server-to-server credentials
218
+
219
+ Publishing this SDK publicly does not grant access to the NexAuth service. The NexAuth backend must enforce application status, subscription entitlement, exact redirect URI matching, allowed origin checks, PKCE S256, authorization-code ownership and expiry, single use, membership, and permissions.
220
+
221
+ ## Package relationship
222
+
223
+ ```text
224
+ Customer application
225
+ └─ @nexauthxyz/react (optional React integration)
226
+ └─ @nexauthxyz/browser
227
+ └─ jose
228
+ └─ NexAuth OIDC endpoints
229
+ ```
230
+
231
+ Use this package directly for framework-independent applications. React applications should normally use `@nexauthxyz/react`, which wraps this client.
232
+
233
+ ## Current session-renewal behavior
234
+
235
+ The SDK detects expired sessions but does not silently renew them unless a secure refresh-token flow is implemented and enabled by the NexAuth backend. Do not simulate refresh by extending browser expiry timestamps. Token renewal must be backed by a real server-side `refresh_token` grant with rotation and replay protection.
236
+
237
+ ## Troubleshooting
238
+
239
+ ### Redirect URI is not registered
240
+
241
+ Register the full URI, including scheme, hostname, port, path, and trailing-slash behavior. These are different values:
242
+
243
+ ```text
244
+ http://localhost:5173
245
+ http://localhost:5173/auth/callback
246
+ ```
247
+
248
+ ### Origin is not registered
249
+
250
+ Register only the origin where NexAuth expects an allowed origin:
251
+
252
+ ```text
253
+ http://localhost:5173
254
+ ```
255
+
256
+ Do not include `/auth/callback` in an allowed-origin record.
257
+
258
+ ### Issuer mismatch
259
+
260
+ The configured issuer must exactly match discovery and token `iss` claims. Avoid mixing local and production issuers.
261
+
262
+ ### Application has no active plan
263
+
264
+ This is a backend entitlement decision. Activate an eligible organization subscription; changing frontend code or the public client ID cannot bypass it.
265
+
266
+ ## Publishing
267
+
268
+ Build and inspect the package before publishing:
269
+
270
+ ```bash
271
+ npm run test --workspace @nexauthxyz/browser
272
+ npm run typecheck --workspace @nexauthxyz/browser
273
+ npm run build --workspace @nexauthxyz/browser
274
+ npm pack --workspace @nexauthxyz/browser --dry-run
275
+ npm publish --workspace @nexauthxyz/browser --access public
276
+ ```
277
+
278
+ Publish `@nexauthxyz/browser` before publishing a version of `@nexauthxyz/react` that depends on it.
279
+
280
+ ## License
281
+
282
+ MIT