@nuria-tech/auth-sdk
v1.1.0
Published
Browser OAuth 2.0 Authorization Code + PKCE client SDK — redirect-only, no password flows
Maintainers
Readme
@nuria-tech/auth-sdk
TypeScript SDK for OAuth 2.0 Authorization Code + PKCE, focused on browser apps and framework integrations (React, Vue, Nuxt, Next, Angular).
Why this SDK
- PKCE S256 + state validation by default
- Redirect (PKCE) and direct credential flows (password, Google, code)
- Optional automatic refresh with concurrency dedupe
- Storage adapters for browser/SSR scenarios
- Framework helpers in dedicated entrypoints
Minimal Requirements
- ECMAScript Target: ES2018
- Browsers: Any modern browser with support for Fetch API, URLSearchParams, and Web Crypto API
- Node.js: >= 20.0.0 (required natively by package.json)
Important note on legacy Node.js (e.g., Node 18 or other past versions)
The Node.js >= 20.0.0 exists because the SDK relies onn the global fetch and crypto (Web Crypto API) objects. Older Node versions lack native support for these APIs.
- If you use SSR: Executing the authentication flow on server using Node < 20.0.0 will cause fatal errors.
- If you have an SPA or legacy project (Nuxt2, Vue2): If the authentication runs completely in the user's browser, it is safe to use this SDK. Node will be used strictly to compile the project.
Because modern framework versions are mapped as optional peer dependencies, modern npm versions (v7+) might throw an ERESOLVE conflict when installing this SDK iln egacy projects. To bypass both the engine lock - using Node < 20 strictly for build purposes - and the peer dependency conflicts, use the --legacy-peer-deps flag.
Frameworks and libraries for specific entrypoints (See 'Entrypoints' flag below):
- React: >= 18.0
- Vue: >= 3.3
- Angular: >= 16.0
- Next.js: >= 13.0
- Nuxt: >= 3.0
- RxJS: >= 7.8
Installation
npm install @nuria-tech/auth-sdkor for older node versions (Node < 20), strictly for build purposes without SSR:
npm install @nuria-tech/auth-sdk --legacy-peer-depsPublished on npm.
Entrypoints
@nuria-tech/auth-sdk: core client + adapters@nuria-tech/auth-sdk/react:useAuthSession,AuthProvider,useAuth@nuria-tech/auth-sdk/vue:useAuthSessioncomposable@nuria-tech/auth-sdk/nuxt: Nuxt cookie adapter helpers@nuria-tech/auth-sdk/next: Next cookie adapter helpers@nuria-tech/auth-sdk/angular:createAngularAuthFacade(RxJS facade) +createBearerInterceptor(HttpInterceptorFn)
Auth flows matrix
| Flow | Backend endpoint(s) | SDK method(s) | Result |
|---|---|---|---|
| Google | POST /v2/google | loginWithGoogle(...) | Session tokens |
| Login + password | POST /v2/login | loginWithPassword(...) | Session tokens |
| Code sent (default) | POST /v2/login-code/challenge + POST /v2/2fa/verify-login | loginWithCodeSent(...) + completeLoginWithCode(...) | Session tokens after code verify |
| Password reset request | POST /v2/password/reset | resetPassword({ email }) | void — sends reset email |
| Password recovery | POST /v2/password/recover | recoverPassword({ token, newPassword }) | void — resets password using token |
| Change password | PATCH /v2/me/password | changePassword({ oldPassword, newPassword }) | void — requires active session |
Example apps
examples/reactexamples/vueexamples/nuxtexamples/nextexamples/angular
Core quick start
import { createAuthClient } from '@nuria-tech/auth-sdk';
const auth = createAuthClient({
clientId: 'your-client-id',
redirectUri: `${window.location.origin}/callback`,
});
await auth.startLogin();
// callback route
await auth.handleRedirectCallback(window.location.href);
const token = await auth.getAccessToken();
console.log(token);Default login flow (login code sent)
const challenge = await auth.startLoginCodeChallenge({
email: '[email protected]',
// optional: channel defaults to 'email'
// channel: 'sms',
});
const session = await auth.verifyLoginCode({
challengeId: challenge.challengeId,
code: '123456',
});Aliases with clearer naming:
await auth.loginWithCodeSent({ email: '[email protected]' });
await auth.completeLoginWithCode({ challengeId: '...', code: '123456' });React quick start
import { createAuthClient } from '@nuria-tech/auth-sdk';
import { AuthProvider, useAuth } from '@nuria-tech/auth-sdk/react';
const auth = createAuthClient({
clientId: 'your-client-id',
redirectUri: `${window.location.origin}/callback`,
});
function AppContent() {
const { session, isLoading, login, logout } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!session) return <button onClick={() => login()}>Login</button>;
return <button onClick={() => logout()}>Logout</button>;
}
export function App() {
return (
<AuthProvider auth={auth}>
<AppContent />
</AuthProvider>
);
}Vue quick start
import { createAuthClient } from '@nuria-tech/auth-sdk';
import { useAuthSession } from '@nuria-tech/auth-sdk/vue';
const auth = createAuthClient({
clientId: 'your-client-id',
redirectUri: `${window.location.origin}/callback`,
});
export function usePageAuth() {
const { session, isLoading, refresh } = useAuthSession(auth);
return { session, isLoading, refresh };
}Nuxt quick start
import { createNuxtAuthClient } from '@nuria-tech/auth-sdk/nuxt';
import { useCookie } from '#app';
const auth = createNuxtAuthClient(
{
clientId: process.env.NUXT_PUBLIC_AUTH_CLIENT_ID!,
redirectUri: process.env.NUXT_PUBLIC_AUTH_CALLBACK_URL!,
},
{
get: (name) => useCookie<string | null>(name).value,
set: (name, value) => {
useCookie<string | null>(name).value = value;
},
remove: (name) => {
useCookie<string | null>(name).value = null;
},
},
);Next quick start
import { createNextAuthClient } from '@nuria-tech/auth-sdk/next';
import { cookies } from 'next/headers';
export function createServerAuth() {
const cookieStore = cookies();
return createNextAuthClient(
{
clientId: process.env.NEXT_PUBLIC_AUTH_CLIENT_ID!,
redirectUri: process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL!,
},
{
get: (name) => cookieStore.get(name)?.value,
set: (name, value) => cookieStore.set(name, value),
remove: (name) => cookieStore.delete(name),
},
);
}Angular quick start
import { Injectable } from '@angular/core';
import { createAuthClient } from '@nuria-tech/auth-sdk';
import { createAngularAuthFacade } from '@nuria-tech/auth-sdk/angular';
@Injectable({ providedIn: 'root' })
export class AuthService {
private auth = createAuthClient({
clientId: 'your-client-id',
redirectUri: `${window.location.origin}/callback`,
});
private facade = createAngularAuthFacade(this.auth);
state$ = this.facade.state$;
login() {
return this.facade.login();
}
logout() {
return this.facade.logout();
}
}Full Angular example (service + guard + callback route + status component):
examples/angular
Defaults
baseUrl:https://ms-auth-v2.nuria.com.brauthorizationEndpoint:${baseUrl}/v2/oauth/authorizetokenEndpoint:${baseUrl}/v2/oauth/tokenuserinfoEndpoint:${baseUrl}/v2/oauth/userinfoscope:openid profile emailenableRefreshToken:true
Configuration
interface AuthConfig {
clientId: string;
redirectUri: string;
baseUrl?: string;
authorizationEndpoint?: string;
tokenEndpoint?: string;
scope?: string;
logoutEndpoint?: string;
userinfoEndpoint?: string;
storage?: StorageAdapter;
transport?: AuthTransport;
onRedirect?: (url: string) => void | Promise<void>;
enableRefreshToken?: boolean;
now?: () => number;
}Storage strategy
| Adapter | Persists reload | JS-readable | SSR |
|---|---|---|---|
| MemoryStorageAdapter | No | Yes | No |
| WebStorageAdapter(sessionStorage) | Per tab | Yes | No |
| WebStorageAdapter(localStorage) | Yes | Yes | No |
| CookieStorageAdapter | Configurable | Depends on cookie flags | Yes |
Security notes
- Do not use
clientSecretin browser/mobile apps. - Prefer memory storage when possible.
- Keep refresh on cookies (
HttpOnly) server-side when available. logout({ returnTo })acceptshttps://URLs, plushttp://localhost|127.0.0.1|[::1]for local dev only.logout({ returnTo })rejects URLs with embedded credentials (user:pass@host).isAuthenticated()returnstruewhen the token is expired butenableRefreshToken: true—getAccessToken()will silently renew it.getClaims()decodes the JWT payload client-side viaatob()without verifying the signature — trust comes from the server that issued the token.- Browser cookie storage encodes/decodes values safely (
encodeURIComponent/decodeURIComponent).
Full policy and reporting process: SECURITY.md.
Public API
interface AuthClient {
init(): Promise<void>;
startLogin(options?: StartLoginOptions): Promise<void>;
handleRedirectCallback(callbackUrl?: string): Promise<Session>;
getSession(): Session | null;
getAccessToken(): Promise<string | null>;
logout(options?: { returnTo?: string }): Promise<void>;
isAuthenticated(): boolean;
onAuthStateChanged(handler: (session: Session | null) => void): () => void;
getClaims(): TokenClaims | null;
hasRole(role: string): boolean;
hasGroup(group: string): boolean;
getUserinfo(): Promise<Record<string, unknown>>;
startLoginCodeChallenge(options: LoginCodeChallengeOptions): Promise<TwoFactorChallenge>;
verifyLoginCode(options: VerifyLoginCodeOptions): Promise<Session>;
loginWithCodeSent(options: LoginCodeChallengeOptions): Promise<TwoFactorChallenge>;
completeLoginWithCode(options: VerifyLoginCodeOptions): Promise<Session>;
loginWithGoogle(options: GoogleLoginOptions): Promise<Session>;
loginWithPassword(options: PasswordLoginOptions): Promise<Session>;
resetPassword(options: { email: string }): Promise<void>;
recoverPassword(options: { token: string; newPassword: string }): Promise<void>;
changePassword(options: { oldPassword: string; newPassword: string }): Promise<void>;
}CI and publish
- PR/main runs: typecheck, lint, test, build
- Tag
v*runs publish workflow with Trusted Publishing
Publish flow:
- Update
versioninpackage.json - Tag and push (
git tag vX.Y.Z && git push --tags) - Workflow validates and publishes
License
MIT - see LICENSE.
