A framework-agnostic web authentication SDK for ForgeBase with SSR support.
- 🌐 Framework-agnostic - works with any JavaScript framework
- 🖥️ Server-side rendering (SSR) support
- 🔄 Automatic token refresh
- 🍪 Cookie and localStorage support
- 📱 Responsive to different environments
- 🔒 Secure authentication flows
- 🔄 Always fresh user data from the server
- 🧩 Framework-specific integrations (React, Angular, Next.js, Nitro)
- 🔄 Support for both Next.js Pages Router and App Router
- 🚀 Angular integration with signals and standalone components
- 🌐 Nitro framework support (Nuxt, Analog.js, etc.)
- ⚡ TypeScript support
npm install @forgebase/web-auth
# or
yarn add @forgebase/web-auth
import { ForgebaseWebAuth, StorageType } from '@forgebase/web-auth';
// Create a new instance
const auth = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.COOKIE, // or LOCAL_STORAGE or MEMORY
useCookies: true, // Use cookies for authentication
withCredentials: true, // Include credentials in requests
});
// Register a new user
await auth.register({
email: 'user@example.com',
password: 'securePassword123',
name: 'John Doe',
});
// Login
await auth.login({
email: 'user@example.com',
password: 'securePassword123',
});
// Get the current user (from memory)
const user = auth.getCurrentUser(); // Note: This is deprecated
// Get the current user (always fetches fresh data from server)
const user = await auth.getUser();
// Check if authenticated
const isAuthenticated = auth.isAuthenticated();
// Logout
await auth.logout();
// Get the API instance for making authenticated requests
const api = auth.api;
// Make an authenticated request
const response = await api.get('/protected-endpoint');
// Get auth interceptors to apply to another axios instance
const authInterceptors = auth.getAuthInterceptors();
// Apply auth interceptors to your own axios instance
const myAxios = axios.create({ baseURL: 'https://api.example.com' });
auth.applyAuthInterceptors(myAxios);
import React from 'react';
import { ForgebaseWebAuth, AuthProvider, useAuth, StorageType } from '@forgebase/web-auth';
// Create auth instance
const auth = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.COOKIE,
});
// App component
function App() {
return (
<AuthProvider auth={auth}>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
</Routes>
</Router>
</AuthProvider>
);
}
// Login component
function LoginPage() {
const { login, isLoading, error } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
await login({ email, password });
// Redirect to profile page
} catch (err) {
// Error is handled by the hook
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Loading...' : 'Login'}
</button>
{error && <p>{error.message}</p>}
</form>
);
}
// Protected route component
function ProtectedRoute({ children }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return children;
}
// _app.tsx
import { AppProps } from 'next/app';
import { ForgebaseWebAuth, AuthProvider, ServerAuthState } from '@forgebase/web-auth';
// Create auth instance
const auth = new ForgebaseWebAuth({
apiUrl: process.env.NEXT_PUBLIC_API_URL,
storageType: 'cookie',
cookieDomain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
});
function MyApp({ Component, pageProps }: AppProps) {
// Extract auth state from pageProps
const { user, accessToken, refreshToken, ...restPageProps } = pageProps as ServerAuthState & any;
return (
<AuthProvider auth={auth} initialUser={user} initialAccessToken={accessToken} initialRefreshToken={refreshToken}>
<Component {...restPageProps} />
</AuthProvider>
);
}
export default MyApp;
// pages/profile.tsx
import { GetServerSideProps } from 'next';
import { withAuth, useAuth } from '@forgebase/web-auth';
export const getServerSideProps: GetServerSideProps = withAuth(
{
authConfig: {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
storageType: 'cookie',
},
redirectUnauthenticated: '/login',
},
async (context, auth, authState) => {
// You can make authenticated API calls here
// const api = auth.api;
// const data = await api.get('/some-protected-endpoint');
return {
props: {
// Additional props
},
};
}
);
function ProfilePage() {
const { user, logout } = useAuth();
return (
<div>
<h1>Profile</h1>
<p>Welcome, {user?.name}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
export default ProfilePage;
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { authMiddleware } from '@forgebase/web-auth';
export function middleware(request: NextRequest) {
return authMiddleware(request, {
authConfig: {
apiUrl: process.env.NEXT_PUBLIC_API_URL || '',
storageType: 'cookie',
secureCookies: process.env.NODE_ENV === 'production',
},
redirectUnauthenticated: '/login',
publicPaths: ['/login', '/register'],
authPaths: ['/dashboard', '/profile'],
});
}
// app/layout.tsx
import { ReactNode } from 'react';
import { getAppRouterAuthProps } from '@forgebase/web-auth';
import ClientAuthProvider from './components/ClientAuthProvider';
export default function RootLayout({ children }: { children: ReactNode }) {
// Get auth props from server
const authProps = getAppRouterAuthProps();
return (
<html lang="en">
<body>
<ClientAuthProvider initialUser={authProps.user} initialAccessToken={authProps.accessToken} initialRefreshToken={authProps.refreshToken}>
{children}
</ClientAuthProvider>
</body>
</html>
);
}
// app/components/ClientAuthProvider.tsx
('use client');
import { ReactNode } from 'react';
import { AuthProvider, ForgebaseWebAuth, User } from '@forgebase/web-auth';
// Create auth instance on the client
const auth = new ForgebaseWebAuth({
apiUrl: process.env.NEXT_PUBLIC_API_URL || '',
storageType: 'cookie',
});
interface ClientAuthProviderProps {
children: ReactNode;
initialUser: User | null;
initialAccessToken?: string;
initialRefreshToken?: string;
}
export default function ClientAuthProvider({ children, initialUser, initialAccessToken, initialRefreshToken }: ClientAuthProviderProps) {
return (
<AuthProvider auth={auth} initialUser={initialUser} initialAccessToken={initialAccessToken} initialRefreshToken={initialRefreshToken}>
{children}
</AuthProvider>
);
}
// app/dashboard/page.tsx
import { protectRoute, getCurrentUser } from '@forgebase/web-auth';
import DashboardClient from './DashboardClient';
export default function DashboardPage() {
// Protect this route - will redirect if not authenticated
protectRoute();
// Get the current user from server
const user = getCurrentUser();
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user?.name || 'User'}!</p>
{/* Pass to client component for interactive features */}
<DashboardClient initialData={user} />
</div>
);
}
The SDK provides a modern Angular integration with support for signals and standalone components.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideForgebaseAuth } from '@forgebase/web-auth';
import { StorageType } from '@forgebase/web-auth';
export const appConfig: ApplicationConfig = {
providers: [
provideForgebaseAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.COOKIE,
secureCookies: true,
}),
],
};
// login.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ForgebaseAuthService } from '@forgebase/web-auth';
@Component({
selector: 'app-login',
standalone: true,
imports: [FormsModule],
template: `
<div>
<h1>Login</h1>
<form (ngSubmit)="onSubmit()">
<div>
<label for="email">Email</label>
<input type="email" id="email" [(ngModel)]="email" name="email" required />
</div>
<div>
<label for="password">Password</label>
<input type="password" id="password" [(ngModel)]="password" name="password" required />
</div>
<button type="submit" [disabled]="isLoading()">{{ isLoading() ? 'Loading...' : 'Login' }}</button>
</form>
@if (currentError()) {
<div class="error">{{ currentError().message }}</div>
}
</div>
`,
})
export class LoginComponent {
email = '';
password = '';
// Inject the auth service
constructor(private authService: ForgebaseAuthService) {}
// Access signals directly
isLoading = this.authService.isLoading;
currentError = this.authService.currentError;
async onSubmit() {
try {
await this.authService.login({
email: this.email,
password: this.password,
});
// Navigate to dashboard
} catch (error) {
// Error is handled by the service
}
}
}
// auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { ForgebaseAuthService } from '@forgebase/web-auth';
export const authGuard: CanActivateFn = () => {
const authService = inject(ForgebaseAuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
// Redirect to login
return router.parseUrl('/login');
};
The SDK provides integration with Nitro-based frameworks like Nuxt and Analog.js.
// server/middleware/auth.ts (Nuxt example)
import { defineEventHandler } from 'h3';
import { nitroAuthMiddleware } from '@forgebase/web-auth';
export default defineEventHandler((event) => {
nitroAuthMiddleware(event, {
authConfig: {
apiUrl: process.env.API_URL || '',
storageType: 'cookie',
secureCookies: process.env.NODE_ENV === 'production'
},
redirectUnauthenticated: '/login',
publicPaths: ['/login', '/register'],
authPaths: ['/dashboard', '/profile']
});
});
// server/api/protected.ts
import { defineEventHandler } from 'h3';
import {
protectNitroRoute,
getNitroUser,
createNitroAuthenticatedFetch
} from '@forgebase/web-auth';
export default defineEventHandler(async (event) => {
// Protect this route
protectNitroRoute(event);
// Get the current user
const user = getNitroUser(event);
// Create an authenticated fetch
const authenticatedFetch = createNitroAuthenticatedFetch(event);
// Make an authenticated request
const response = await authenticatedFetch('https://api.example.com/data');
const data = await response.json();
return {
user,
data
};
});
// pages/login.vue (Nuxt example)
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const email = ref('');
const password = ref('');
const error = ref(null);
const loading = ref(false);
const router = useRouter();
async function login() {
loading.value = true;
error.value = null;
try {
const response = await $fetch('/api/login', {
method: 'POST',
body: {
email: email.value,
password: password.value
}
});
if (response.success) {
router.push('/dashboard');
}
} catch (err) {
error.value = err.message || 'Login failed';
} finally {
loading.value = false;
}
}
</script>
<template>
<div>
<h1>Login</h1>
<form @submit.prevent="login">
<div>
<label for="email">Email</label>
<input type="email" id="email" v-model="email" required>
</div>
<div>
<label for="password">Password</label>
<input type="password" id="password" v-model="password" required>
</div>
<button type="submit" :disabled="loading">{{ loading ? 'Loading...' : 'Login' }}</button>
</form>
<p v-if="error" class="error">{{ error }}</p>
</div>
</template>
// Send verification email
const { sendVerificationEmail } = useAuth();
await sendVerificationEmail('user@example.com', 'https://example.com/verify');
// Verify email
const { verifyEmail } = useAuth();
await verifyEmail(userId, verificationCode);
// Request password reset
const { forgotPassword } = useAuth();
await forgotPassword('user@example.com', 'https://example.com/reset-password');
// Verify reset token
const { auth } = useAuth();
const isValid = await auth.verifyResetToken(userId, resetToken);
// Reset password
const { resetPassword } = useAuth();
await resetPassword(userId, resetToken, 'newSecurePassword123');
The SDK supports different storage mechanisms:
import { ForgebaseWebAuth, StorageType } from '@forgebase/web-auth';
// Use cookies (default)
const auth1 = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.COOKIE,
cookieDomain: 'example.com',
secureCookies: true,
httpOnlyCookies: true,
sameSite: 'lax',
});
// Use localStorage
const auth2 = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.LOCAL_STORAGE,
});
// Use in-memory storage (for SSR or when storage is not available)
const auth3 = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storageType: StorageType.MEMORY,
});
// Use custom storage
const auth4 = new ForgebaseWebAuth({
apiUrl: 'https://api.example.com',
storage: myCustomStorage, // Implements AuthStorage interface
});
The SDK automatically detects if it's running in an SSR environment and adjusts its behavior accordingly:
- In SSR environments, it won't try to access browser-specific APIs
- It will use cookies for authentication in SSR environments
- It provides utilities for hydrating the auth state from server to client
The SDK exposes the configured axios instance with all authentication interceptors:
// Get the axios instance
const { getApi } = useAuth();
const api = getApi();
// Make authenticated API calls
const response = await api.get('/protected-resource');
import { AuthErrorType } from '@forgebase/web-auth';
try {
await auth.login({ email, password });
} catch (error) {
switch (error.type) {
case AuthErrorType.INVALID_CREDENTIALS:
// Handle invalid credentials
break;
case AuthErrorType.VERIFICATION_REQUIRED:
// Handle verification required
break;
case AuthErrorType.NETWORK_ERROR:
// Handle network error
break;
case AuthErrorType.SSR_ERROR:
// Handle SSR-specific error
break;
default:
// Handle other errors
break;
}
}
MIT