@nuria-tech/auth-sdk 1.0.0 → 1.0.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Nuria Tech
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nuria Tech
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
@@ -1,270 +1,270 @@
1
- # @nuria-tech/auth-sdk
2
-
3
- A minimal, browser-native TypeScript SDK for OAuth 2.0 Authorization Code flow with PKCE. Redirect-only — no embedded UI, no password flows.
4
-
5
- ## Features
6
-
7
- - Authorization Code flow + PKCE (S256, mandatory)
8
- - State parameter generation and validation
9
- - Token exchange via standard form-encoded request
10
- - Automatic token refresh (optional)
11
- - Pluggable storage adapters (memory, sessionStorage, cookies)
12
- - Configurable HTTP transport with retry and interceptors
13
- - Zero production dependencies — uses Web Crypto API and fetch
14
-
15
- ## Installation
16
-
17
- ```bash
18
- npm install @nuria-tech/auth-sdk
19
- ```
20
-
21
- Published on [npm](https://www.npmjs.com/package/@nuria-tech/auth-sdk).
22
-
23
- ## Quick start
24
-
25
- ```ts
26
- import { createAuthClient } from '@nuria-tech/auth-sdk';
27
-
28
- const auth = createAuthClient({
29
- clientId: 'your-client-id',
30
- authorizationEndpoint: 'https://your-auth-server.example.com/authorize',
31
- tokenEndpoint: 'https://your-auth-server.example.com/token',
32
- redirectUri: 'https://your-app.example.com/callback',
33
- scope: 'openid profile email',
34
- });
35
-
36
- // Redirect to login
37
- await auth.startLogin();
38
-
39
- // In your callback page
40
- const session = await auth.handleRedirectCallback(window.location.href);
41
- console.log(session.tokens.accessToken);
42
- ```
43
-
44
- ## Configuration
45
-
46
- ```ts
47
- interface AuthConfig {
48
- // Required
49
- clientId: string;
50
- authorizationEndpoint: string;
51
- tokenEndpoint: string;
52
- redirectUri: string;
53
-
54
- // Optional
55
- scope?: string; // default scope sent with every login
56
- logoutEndpoint?: string; // if set, logout() redirects here
57
- userinfoEndpoint?: string; // required for getUserinfo()
58
- storage?: StorageAdapter; // default: MemoryStorageAdapter
59
- transport?: AuthTransport; // default: FetchAuthTransport
60
- onRedirect?: (url: string) => void | Promise<void>; // override browser redirect
61
- enableRefreshToken?: boolean; // enable automatic token refresh
62
- now?: () => number; // override Date.now() for testing
63
- }
64
- ```
65
-
66
- > **Security note:** Do not include `clientSecret` in browser apps. This SDK is designed for public clients (SPAs, mobile). PKCE provides the proof of possession without a client secret.
67
-
68
- ## Public API
69
-
70
- ```ts
71
- interface AuthClient {
72
- startLogin(options?: StartLoginOptions): Promise<void>;
73
- handleRedirectCallback(callbackUrl?: string): Promise<Session>;
74
- getSession(): Session | null;
75
- getAccessToken(): Promise<string | null>;
76
- logout(options?: { returnTo?: string }): Promise<void>;
77
- isAuthenticated(): boolean;
78
- onAuthStateChanged(handler: (session: Session | null) => void): () => void;
79
- getUserinfo(): Promise<Record<string, unknown>>;
80
- }
81
- ```
82
-
83
- ### `startLogin(options?)`
84
-
85
- Generates PKCE `code_verifier` + `code_challenge` (S256), stores them in the configured storage, and redirects to `authorizationEndpoint`. The `state` parameter is always included and validated on callback.
86
-
87
- ```ts
88
- await auth.startLogin({
89
- scopes: ['openid', 'profile'], // overrides config.scope
90
- loginHint: 'user@example.com',
91
- extraParams: { prompt: 'login' },
92
- });
93
- ```
94
-
95
- ### `handleRedirectCallback(callbackUrl?)`
96
-
97
- Parses the callback URL, validates `state`, exchanges `code` for tokens using a form-encoded POST to `tokenEndpoint`, and clears transient PKCE storage. Throws typed `AuthError` on any failure.
98
-
99
- ```ts
100
- // In your /callback route
101
- const session = await auth.handleRedirectCallback(window.location.href);
102
- ```
103
-
104
- ### `getAccessToken()`
105
-
106
- Returns the current access token. If the token is expired and `enableRefreshToken: true`, automatically refreshes it (concurrent calls are deduplicated).
107
-
108
- ### `logout(options?)`
109
-
110
- Clears the local session. If `logoutEndpoint` is configured, redirects to it. Validates `returnTo` to prevent open redirect attacks.
111
-
112
- ```ts
113
- await auth.logout({ returnTo: 'https://your-app.example.com' });
114
- ```
115
-
116
- ### `onAuthStateChanged(handler)`
117
-
118
- Subscribes to session changes. Returns an unsubscribe function.
119
-
120
- ```ts
121
- const unsubscribe = auth.onAuthStateChanged((session) => {
122
- console.log(session ? 'logged in' : 'logged out');
123
- });
124
- ```
125
-
126
- ## Storage adapters
127
-
128
- Default is `MemoryStorageAdapter` (most secure for XSS resistance, clears on reload).
129
-
130
- ```ts
131
- import { WebStorageAdapter } from '@nuria-tech/auth-sdk';
132
-
133
- // sessionStorage: persists per tab, JS-readable
134
- const auth = createAuthClient({
135
- ...config,
136
- storage: new WebStorageAdapter(window.sessionStorage),
137
- });
138
- ```
139
-
140
- Cookie adapter for SSR:
141
-
142
- ```ts
143
- import { CookieStorageAdapter } from '@nuria-tech/auth-sdk';
144
-
145
- const auth = createAuthClient({
146
- ...config,
147
- storage: new CookieStorageAdapter({
148
- getCookie: async (name) => getCookieFromRequest(name),
149
- setCookie: async (name, value) => setResponseCookie(name, value),
150
- removeCookie: async (name) => clearResponseCookie(name),
151
- }),
152
- });
153
- ```
154
-
155
- ## Transport customization
156
-
157
- ```ts
158
- import { FetchAuthTransport } from '@nuria-tech/auth-sdk';
159
-
160
- const auth = createAuthClient({
161
- ...config,
162
- transport: new FetchAuthTransport({
163
- timeoutMs: 10_000,
164
- retries: 1,
165
- interceptors: [
166
- {
167
- onRequest: async (_url, req) => ({
168
- ...req,
169
- headers: { ...(req.headers ?? {}), 'X-App-Version': '1.0.0' },
170
- }),
171
- },
172
- ],
173
- }),
174
- });
175
- ```
176
-
177
- ## React example
178
-
179
- ```tsx
180
- import { useMemo, useState, useEffect } from 'react';
181
- import { createAuthClient } from '@nuria-tech/auth-sdk';
182
-
183
- const auth = createAuthClient({
184
- clientId: 'your-client-id',
185
- authorizationEndpoint: 'https://auth.example.com/authorize',
186
- tokenEndpoint: 'https://auth.example.com/token',
187
- redirectUri: `${window.location.origin}/callback`,
188
- });
189
-
190
- export function App() {
191
- const [session, setSession] = useState(auth.getSession());
192
-
193
- useEffect(() => auth.onAuthStateChanged(setSession), []);
194
-
195
- if (!session) {
196
- return <button onClick={() => auth.startLogin()}>Login</button>;
197
- }
198
- return <button onClick={() => auth.logout()}>Logout</button>;
199
- }
200
- ```
201
-
202
- ## Next.js SSR example
203
-
204
- ```ts
205
- // lib/auth.ts
206
- import { createAuthClient, CookieStorageAdapter } from '@nuria-tech/auth-sdk';
207
-
208
- export function createServerAuth(cookieApi: {
209
- get: (name: string) => string | undefined;
210
- set: (name: string, value: string) => void;
211
- delete: (name: string) => void;
212
- }) {
213
- return createAuthClient({
214
- clientId: process.env.NEXT_PUBLIC_AUTH_CLIENT_ID!,
215
- authorizationEndpoint: `${process.env.NEXT_PUBLIC_AUTH_BASE_URL}/authorize`,
216
- tokenEndpoint: `${process.env.NEXT_PUBLIC_AUTH_BASE_URL}/token`,
217
- redirectUri: process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL!,
218
- storage: new CookieStorageAdapter({
219
- getCookie: async (name) => cookieApi.get(name) ?? null,
220
- setCookie: async (name, value) => cookieApi.set(name, value),
221
- removeCookie: async (name) => cookieApi.delete(name),
222
- }),
223
- onRedirect: (url) => { redirect(url); },
224
- });
225
- }
226
- ```
227
-
228
- ## Security notes
229
-
230
- - **No secrets:** Never include a `clientSecret` in browser/mobile apps. This SDK supports public clients only.
231
- - **PKCE is mandatory:** S256 code challenge is always used. Plain PKCE is not supported.
232
- - **State validation:** State is always generated and validated on callback.
233
- - **Memory storage default:** Tokens are stored in memory by default to minimize XSS exposure.
234
- - **Use sessionStorage cautiously:** Survives page reload but is still JS-readable.
235
- - **Avoid localStorage** for sensitive tokens.
236
- - **Validate `returnTo`:** The `logout()` method rejects non-`https://` or `http://` `returnTo` values.
237
-
238
- ## Storage trade-offs
239
-
240
- | Adapter | Persists reload | XSS risk | SSR compatible |
241
- |---------|----------------|----------|----------------|
242
- | `MemoryStorageAdapter` | No | Lowest | No |
243
- | `WebStorageAdapter(sessionStorage)` | Per tab | Medium | No |
244
- | `WebStorageAdapter(localStorage)` | Yes | High | No |
245
- | `CookieStorageAdapter` | Configurable | Low (httpOnly) | Yes |
246
-
247
- ## Troubleshooting
248
-
249
- - **State mismatch:** Ensure the same storage instance/tab is used between `startLogin()` and `handleRedirectCallback()`. Clear `nuria:oauth:state` and retry.
250
- - **Missing code_verifier:** The `nuria:oauth:code_verifier` key was not found in storage. Ensure `startLogin()` was called before `handleRedirectCallback()`.
251
- - **Token refresh fails:** Ensure `enableRefreshToken: true` is set and the auth server supports the `refresh_token` grant for your client.
252
-
253
- ## CI/CD
254
-
255
- This repository uses GitHub Actions (`.github/workflows/ci-publish.yml`):
256
-
257
- - **PR and `main` push:** typecheck, lint, test (coverage), build — runs on Node 20, 22
258
- - **Tag `v*` push:** validates tag matches `package.json` version, then publishes to npm via Trusted Publishing (OIDC — no stored tokens)
259
-
260
- ### Publishing
261
-
262
- 1. Update `version` in `package.json`
263
- 2. Push a tag: `git tag v1.0.0 && git push --tags`
264
- 3. The workflow validates the version and publishes to npm
265
-
266
- > **One-time setup:** after the first manual publish, configure Trusted Publishing at **npmjs.com → package → Settings → Automated Publishing** with repository `nuria-tech/nuria-auth-sdk` and workflow `ci-publish.yml`.
267
-
268
- ## License
269
-
270
- MIT — see [LICENSE](./LICENSE).
1
+ # @nuria-tech/auth-sdk
2
+
3
+ A minimal, browser-native TypeScript SDK for OAuth 2.0 Authorization Code flow with PKCE. Redirect-only — no embedded UI, no password flows.
4
+
5
+ ## Features
6
+
7
+ - Authorization Code flow + PKCE (S256, mandatory)
8
+ - State parameter generation and validation
9
+ - Token exchange via standard form-encoded request
10
+ - Automatic token refresh (optional)
11
+ - Pluggable storage adapters (memory, sessionStorage, cookies)
12
+ - Configurable HTTP transport with retry and interceptors
13
+ - Zero production dependencies — uses Web Crypto API and fetch
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @nuria-tech/auth-sdk
19
+ ```
20
+
21
+ Published on [npm](https://www.npmjs.com/package/@nuria-tech/auth-sdk).
22
+
23
+ ## Quick start
24
+
25
+ ```ts
26
+ import { createAuthClient } from '@nuria-tech/auth-sdk';
27
+
28
+ const auth = createAuthClient({
29
+ clientId: 'your-client-id',
30
+ authorizationEndpoint: 'https://your-auth-server.example.com/authorize',
31
+ tokenEndpoint: 'https://your-auth-server.example.com/token',
32
+ redirectUri: 'https://your-app.example.com/callback',
33
+ scope: 'openid profile email',
34
+ });
35
+
36
+ // Redirect to login
37
+ await auth.startLogin();
38
+
39
+ // In your callback page
40
+ const session = await auth.handleRedirectCallback(window.location.href);
41
+ console.log(session.tokens.accessToken);
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ ```ts
47
+ interface AuthConfig {
48
+ // Required
49
+ clientId: string;
50
+ authorizationEndpoint: string;
51
+ tokenEndpoint: string;
52
+ redirectUri: string;
53
+
54
+ // Optional
55
+ scope?: string; // default scope sent with every login
56
+ logoutEndpoint?: string; // if set, logout() redirects here
57
+ userinfoEndpoint?: string; // required for getUserinfo()
58
+ storage?: StorageAdapter; // default: MemoryStorageAdapter
59
+ transport?: AuthTransport; // default: FetchAuthTransport
60
+ onRedirect?: (url: string) => void | Promise<void>; // override browser redirect
61
+ enableRefreshToken?: boolean; // enable automatic token refresh
62
+ now?: () => number; // override Date.now() for testing
63
+ }
64
+ ```
65
+
66
+ > **Security note:** Do not include `clientSecret` in browser apps. This SDK is designed for public clients (SPAs, mobile). PKCE provides the proof of possession without a client secret.
67
+
68
+ ## Public API
69
+
70
+ ```ts
71
+ interface AuthClient {
72
+ startLogin(options?: StartLoginOptions): Promise<void>;
73
+ handleRedirectCallback(callbackUrl?: string): Promise<Session>;
74
+ getSession(): Session | null;
75
+ getAccessToken(): Promise<string | null>;
76
+ logout(options?: { returnTo?: string }): Promise<void>;
77
+ isAuthenticated(): boolean;
78
+ onAuthStateChanged(handler: (session: Session | null) => void): () => void;
79
+ getUserinfo(): Promise<Record<string, unknown>>;
80
+ }
81
+ ```
82
+
83
+ ### `startLogin(options?)`
84
+
85
+ Generates PKCE `code_verifier` + `code_challenge` (S256), stores them in the configured storage, and redirects to `authorizationEndpoint`. The `state` parameter is always included and validated on callback.
86
+
87
+ ```ts
88
+ await auth.startLogin({
89
+ scopes: ['openid', 'profile'], // overrides config.scope
90
+ loginHint: 'user@example.com',
91
+ extraParams: { prompt: 'login' },
92
+ });
93
+ ```
94
+
95
+ ### `handleRedirectCallback(callbackUrl?)`
96
+
97
+ Parses the callback URL, validates `state`, exchanges `code` for tokens using a form-encoded POST to `tokenEndpoint`, and clears transient PKCE storage. Throws typed `AuthError` on any failure.
98
+
99
+ ```ts
100
+ // In your /callback route
101
+ const session = await auth.handleRedirectCallback(window.location.href);
102
+ ```
103
+
104
+ ### `getAccessToken()`
105
+
106
+ Returns the current access token. If the token is expired and `enableRefreshToken: true`, automatically refreshes it (concurrent calls are deduplicated).
107
+
108
+ ### `logout(options?)`
109
+
110
+ Clears the local session. If `logoutEndpoint` is configured, redirects to it. Validates `returnTo` to prevent open redirect attacks.
111
+
112
+ ```ts
113
+ await auth.logout({ returnTo: 'https://your-app.example.com' });
114
+ ```
115
+
116
+ ### `onAuthStateChanged(handler)`
117
+
118
+ Subscribes to session changes. Returns an unsubscribe function.
119
+
120
+ ```ts
121
+ const unsubscribe = auth.onAuthStateChanged((session) => {
122
+ console.log(session ? 'logged in' : 'logged out');
123
+ });
124
+ ```
125
+
126
+ ## Storage adapters
127
+
128
+ Default is `MemoryStorageAdapter` (most secure for XSS resistance, clears on reload).
129
+
130
+ ```ts
131
+ import { WebStorageAdapter } from '@nuria-tech/auth-sdk';
132
+
133
+ // sessionStorage: persists per tab, JS-readable
134
+ const auth = createAuthClient({
135
+ ...config,
136
+ storage: new WebStorageAdapter(window.sessionStorage),
137
+ });
138
+ ```
139
+
140
+ Cookie adapter for SSR:
141
+
142
+ ```ts
143
+ import { CookieStorageAdapter } from '@nuria-tech/auth-sdk';
144
+
145
+ const auth = createAuthClient({
146
+ ...config,
147
+ storage: new CookieStorageAdapter({
148
+ getCookie: async (name) => getCookieFromRequest(name),
149
+ setCookie: async (name, value) => setResponseCookie(name, value),
150
+ removeCookie: async (name) => clearResponseCookie(name),
151
+ }),
152
+ });
153
+ ```
154
+
155
+ ## Transport customization
156
+
157
+ ```ts
158
+ import { FetchAuthTransport } from '@nuria-tech/auth-sdk';
159
+
160
+ const auth = createAuthClient({
161
+ ...config,
162
+ transport: new FetchAuthTransport({
163
+ timeoutMs: 10_000,
164
+ retries: 1,
165
+ interceptors: [
166
+ {
167
+ onRequest: async (_url, req) => ({
168
+ ...req,
169
+ headers: { ...(req.headers ?? {}), 'X-App-Version': '1.0.0' },
170
+ }),
171
+ },
172
+ ],
173
+ }),
174
+ });
175
+ ```
176
+
177
+ ## React example
178
+
179
+ ```tsx
180
+ import { useMemo, useState, useEffect } from 'react';
181
+ import { createAuthClient } from '@nuria-tech/auth-sdk';
182
+
183
+ const auth = createAuthClient({
184
+ clientId: 'your-client-id',
185
+ authorizationEndpoint: 'https://auth.example.com/authorize',
186
+ tokenEndpoint: 'https://auth.example.com/token',
187
+ redirectUri: `${window.location.origin}/callback`,
188
+ });
189
+
190
+ export function App() {
191
+ const [session, setSession] = useState(auth.getSession());
192
+
193
+ useEffect(() => auth.onAuthStateChanged(setSession), []);
194
+
195
+ if (!session) {
196
+ return <button onClick={() => auth.startLogin()}>Login</button>;
197
+ }
198
+ return <button onClick={() => auth.logout()}>Logout</button>;
199
+ }
200
+ ```
201
+
202
+ ## Next.js SSR example
203
+
204
+ ```ts
205
+ // lib/auth.ts
206
+ import { createAuthClient, CookieStorageAdapter } from '@nuria-tech/auth-sdk';
207
+
208
+ export function createServerAuth(cookieApi: {
209
+ get: (name: string) => string | undefined;
210
+ set: (name: string, value: string) => void;
211
+ delete: (name: string) => void;
212
+ }) {
213
+ return createAuthClient({
214
+ clientId: process.env.NEXT_PUBLIC_AUTH_CLIENT_ID!,
215
+ authorizationEndpoint: `${process.env.NEXT_PUBLIC_AUTH_BASE_URL}/authorize`,
216
+ tokenEndpoint: `${process.env.NEXT_PUBLIC_AUTH_BASE_URL}/token`,
217
+ redirectUri: process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL!,
218
+ storage: new CookieStorageAdapter({
219
+ getCookie: async (name) => cookieApi.get(name) ?? null,
220
+ setCookie: async (name, value) => cookieApi.set(name, value),
221
+ removeCookie: async (name) => cookieApi.delete(name),
222
+ }),
223
+ onRedirect: (url) => { redirect(url); },
224
+ });
225
+ }
226
+ ```
227
+
228
+ ## Security notes
229
+
230
+ - **No secrets:** Never include a `clientSecret` in browser/mobile apps. This SDK supports public clients only.
231
+ - **PKCE is mandatory:** S256 code challenge is always used. Plain PKCE is not supported.
232
+ - **State validation:** State is always generated and validated on callback.
233
+ - **Memory storage default:** Tokens are stored in memory by default to minimize XSS exposure.
234
+ - **Use sessionStorage cautiously:** Survives page reload but is still JS-readable.
235
+ - **Avoid localStorage** for sensitive tokens.
236
+ - **Validate `returnTo`:** The `logout()` method rejects non-`https://` or `http://` `returnTo` values.
237
+
238
+ ## Storage trade-offs
239
+
240
+ | Adapter | Persists reload | XSS risk | SSR compatible |
241
+ |---------|----------------|----------|----------------|
242
+ | `MemoryStorageAdapter` | No | Lowest | No |
243
+ | `WebStorageAdapter(sessionStorage)` | Per tab | Medium | No |
244
+ | `WebStorageAdapter(localStorage)` | Yes | High | No |
245
+ | `CookieStorageAdapter` | Configurable | Low (httpOnly) | Yes |
246
+
247
+ ## Troubleshooting
248
+
249
+ - **State mismatch:** Ensure the same storage instance/tab is used between `startLogin()` and `handleRedirectCallback()`. Clear `nuria:oauth:state` and retry.
250
+ - **Missing code_verifier:** The `nuria:oauth:code_verifier` key was not found in storage. Ensure `startLogin()` was called before `handleRedirectCallback()`.
251
+ - **Token refresh fails:** Ensure `enableRefreshToken: true` is set and the auth server supports the `refresh_token` grant for your client.
252
+
253
+ ## CI/CD
254
+
255
+ This repository uses GitHub Actions (`.github/workflows/ci-publish.yml`):
256
+
257
+ - **PR and `main` push:** typecheck, lint, test (coverage), build — runs on Node 20, 22
258
+ - **Tag `v*` push:** validates tag matches `package.json` version, then publishes to npm via Trusted Publishing (OIDC — no stored tokens)
259
+
260
+ ### Publishing
261
+
262
+ 1. Update `version` in `package.json`
263
+ 2. Push a tag: `git tag v1.0.0 && git push --tags`
264
+ 3. The workflow validates the version and publishes to npm
265
+
266
+ > **One-time setup:** after the first manual publish, configure Trusted Publishing at **npmjs.com → package → Settings → Automated Publishing** with repository `nuria-tech/nuria-auth-sdk` and workflow `ci-publish.yml`.
267
+
268
+ ## License
269
+
270
+ MIT — see [LICENSE](./LICENSE).