@grasp-labs/ds-auth-web
v0.7.0
Published
Modern authentication library for React applications with OAuth2, credentials, token sessions, and HTTP-only cookie sessions.
Readme
@grasp-labs/ds-auth-web
Modern authentication library for React applications with OAuth2, credentials, token sessions, and HTTP-only cookie sessions.
Installation
npm install @grasp-labs/ds-auth-webFor beta releases:
npm install @grasp-labs/ds-auth-web@betaQuick Start
import { AuthProvider, useAuth } from "@grasp-labs/ds-auth-web";
function App() {
return (
<AuthProvider
storageType="sessionStorage"
authMode="cookie"
autoRefresh={false}
credentials={{
loginUrl: "/api/auth/login",
mfaUrl: "/api/auth/mfa",
userInfoUrl: "/api/auth/me",
logoutUrl: "/api/auth/logout",
}}
oauth2={{
google: {
clientId: "your-google-client-id",
redirectUri: window.location.origin,
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
tokenUrl: "https://oauth2.googleapis.com/token",
scope: ["openid", "profile", "email"],
},
}}
>
<YourApp />
</AuthProvider>
);
}
function YourApp() {
const { isAuthenticated, user, loginOAuth2, loginWithCredentials, logout } =
useAuth();
if (isAuthenticated) {
return (
<div>
Welcome, {user?.name}!<button onClick={logout}>Sign Out</button>
</div>
);
}
return (
<div>
{/* Credentials login */}
<form
onSubmit={(e) => {
e.preventDefault();
const form = e.currentTarget;
const formData = new FormData(form);
loginWithCredentials({
email: String(formData.get("email") ?? ""),
password: String(formData.get("password") ?? ""),
});
}}
>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign In</button>
</form>
{/* OAuth2 login with provider key */}
<button onClick={() => loginOAuth2("google")}>Sign in with Google</button>
</div>
);
}Features
- 🔐 OAuth2 with PKCE - Secure authorization code flow
- 📝 Credentials login - Email/password with MFA support
- 🍪 HTTP-only cookie sessions - BFF-friendly session mode without exposing tokens to React
- 🔄 Auto token refresh - Keeps sessions alive automatically
- 💾 Flexible storage - Session, local, or memory storage
- 🎣 React 19 ready - No unnecessary hooks or memoization
- 🌐 CORS support - Built-in CORS handling for credentials auth
- 🔌 Axios integration - Auto-inject auth headers with one flag
- 🛡️ Error handling - Typed errors with user-friendly messages
- 📦 Small bundle - ~25KB gzipped
- 🚀 TypeScript - Full type safety
API Reference
useAuth() Hook
The main hook for accessing authentication state and methods:
const {
// State
isAuthenticated: boolean,
user: User | null,
tokens: Tokens | null,
method: 'oauth2' | 'credentials' | null,
error: Error | null,
mfa: MFAState | undefined,
hasCheckedSession: boolean,
// Loading states
isLoggingIn: boolean,
isVerifyingMFA: boolean,
isRefreshing: boolean,
isFetchingUserInfo: boolean,
// Methods
loginOAuth2: (providerKey: string) => Promise<void>,
loginWithCredentials: ({ email, password, rememberMe }) => Promise<Tokens | { mfaRequired: true } | void>,
verifyMFA: (code: string) => Promise<Tokens | void>,
logout: () => Promise<void>,
refresh: () => Promise<boolean>,
// Utilities
getAuthHeader: () => string | null,
} = useAuth();Configuration
AuthProvider accepts individual props instead of a config object:
interface IAuthProviderProps {
children: ReactNode;
// Storage
storageType?: "localStorage" | "sessionStorage" | "memory"; // Default: "sessionStorage"
// Session mode
authMode?: "token" | "cookie"; // Default: "token"
// Auto refresh
autoRefresh?: boolean; // Default: true
refreshBuffer?: number; // Buffer before expiry in seconds
refreshInterval?: number; // Check interval in ms
// OAuth2 providers
oauth2?: Record<string, OAuth2Config>;
// Credentials configuration
credentials?: CredentialsConfig;
// Callbacks
onAuthChange?: (isAuthenticated: boolean) => void;
onError?: (error: Error) => void;
}
interface OAuth2Config {
clientId: string;
redirectUri: string;
authorizationUrl: string;
tokenUrl: string;
userInfoUrl?: string;
scope?: string[];
clientSecret?: string;
usePKCE?: boolean; // Default: true
revokeUrl?: string;
refreshTokenUrl?: string;
}
interface CredentialsConfig {
loginUrl: string;
mfaUrl?: string;
userInfoUrl?: string;
refreshUrl?: string;
logoutUrl?: string;
cors?: boolean;
}HTTP-only Cookie Sessions
Use authMode="cookie" when a backend-for-frontend owns the session and stores tokens in HTTP-only cookies. In this mode:
loginWithCredentialssendsx-auth-mode: cookieandwithCredentials: true.- the backend should set its session cookies and can return
204 No Content; - frontend tokens remain
null; AuthProviderrestores an existing browser session by callingcredentials.userInfoUrlwith credentials when it starts with no local user;hasCheckedSessionbecomestrueonce that startup check completes or is skipped;- a
401fromcredentials.userInfoUrlleaves the user unauthenticated without logging noisy errors.
That bootstrap is useful after a credentials login, an SSO/OIDC callback handled by the BFF, a page reload, or navigation between React apps sharing the same cookie session.
<AuthProvider
authMode="cookie"
autoRefresh={false}
credentials={{
loginUrl: "/api/auth/login",
mfaUrl: "/api/auth/mfa-login",
userInfoUrl: "/api/me",
logoutUrl: "/api/auth/logout",
}}
>
<App />
</AuthProvider>For cross-origin APIs, set credentials.cors=true and make sure the server allows credentialed requests. For same-origin BFF routes, leave cors unset.
Development
Getting Started
Want to contribute or run the project locally? Here's what you need:
Requirements:
- Node.js 22 or higher
- npm or your favorite package manager
Setup:
# Install dependencies
npm install
# (Optional) configure example server secrets
cp examples/.env.example examples/.env.local
# Run the example dev server (loads `examples/.env` automatically)
npm run dev:examples
# Run tests
npm test
# Run tests in watch mode (great for development)
npm run test:watch
# Build the library
npm run buildExternal IDP toggle
The credential form in examples/ includes a "Use External IDP" checkbox powered by the IDP_LOGIN_URL, IDP_MFA_URL, and IDP_USERINFO_URL variables. Point those URLs at any identity provider that exposes compatible JSON endpoints—Grasp DP, Keycloak, Auth0, etc.—and the example app will proxy email/password + MFA requests through your backend instead of the mock database. When the toggle is off, everything runs against the mocked routes so you can develop without touching real infrastructure.
Cookie mode toggle
The example app also includes a "Use HTTP-only cookie mode" checkbox. When enabled, the example server sets HTTP-only cookies instead of returning tokens to React. Reloading the page demonstrates the cookie-session bootstrap: AuthProvider calls the configured userInfoUrl, receives the current user from the backend, and marks the session as authenticated without exposing tokens in browser storage.
The build output goes into the dist/ folder, which is what gets published to npm.
Running Tests
We use Vitest for testing because it's fast and works great with TypeScript:
npm test # Run all tests once
npm run test:watch # Watch mode - tests run as you make changes
npm run test:coverage # Generate a coverage reportCoverage reports are generated as HTML in the coverage/ directory.
Code Quality
Before submitting a PR, make sure your code passes linting and formatting:
npm run lint # Check for issues
npm run lint:fix # Auto-fix what we can
npm run format # Format all files with PrettierLocal Beta Release
To generate and publish a beta release locally:
npm run build
npm version prerelease --preid=beta
npm publish --tag beta