@rqdhw3n/react-auth-flow
v1.0.6
Published
A reusable authentication flow package for React applications
Downloads
985
Maintainers
Readme
@rqdhw3n/react-auth-flow
Reusable auth flows for React apps with packaged UI, route guards, mock mode, theming, and cookie-friendly request handling.
Installation
npm install @rqdhw3n/react-auth-flow react react-dom react-router-domThe package injects its own auth CSS automatically. Consumers do not need Tailwind or a manual CSS import.
Basic setup
import {
AuthLayout,
AuthProvider,
LoginForm,
ProtectedRoute,
} from "@rqdhw3n/react-auth-flow";
import { BrowserRouter, Route, Routes } from "react-router-dom";
function LoginPage() {
return (
<AuthLayout
title="Welcome back"
subtitle="Sign in to continue"
brand={<strong>Acme</strong>}
>
<LoginForm />
</AuthLayout>
);
}
function App() {
return (
<BrowserRouter>
<AuthProvider baseURL="http://localhost:8000/api">
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute redirectTo="/login">
<div>Dashboard</div>
</ProtectedRoute>
}
/>
</Routes>
</AuthProvider>
</BrowserRouter>
);
}baseURL and baseUrl are both supported.
Mock mode local test
Use mock mode when you want the package to work without a backend.
import { AuthProvider } from "@rqdhw3n/react-auth-flow";
export function Root() {
return (
<AuthProvider mock mockStorageKey="rq-auth-user">
<App />
</AuthProvider>
);
}Mock behavior:
login()creates a fake admin user and stores it inlocalStorageregister()creates a fake normal user and stores it inlocalStoragelogout()clears the fake sessionrestoreSession()andme()read the fake session backforgotPassword(),resetPassword(), andverifyEmail()return successverifyTwoFactor()accepts any code with the configured length
Custom mock user:
<AuthProvider
mock
mockStorageKey="rq-auth-user"
mockUser={{
id: 99,
name: "Demo User",
email: "[email protected]",
roles: ["admin"],
permissions: ["users.manage", "billing.edit"],
}}
>
<App />
</AuthProvider>Custom requestAdapter
If mock is true, mock mode wins. Otherwise the provider uses:
requestAdapter- built-in
fetch
import { AuthProvider, type AuthRequestAdapter } from "@rqdhw3n/react-auth-flow";
import axios from "axios";
const requestAdapter: AuthRequestAdapter = async ({
endpoint,
method,
data,
headers,
}) => {
const response = await axios({
url: `http://localhost:8000/api${endpoint}`,
method,
data,
withCredentials: true,
headers,
});
return response.data;
};
<AuthProvider requestAdapter={requestAdapter}>
<App />
</AuthProvider>;Theme customization
Theme values are applied through CSS variables on the provider wrapper.
<AuthProvider
baseURL="http://localhost:8000/api"
theme={{
primaryColor: "#0f766e",
primaryHoverColor: "#115e59",
radius: "20px",
fontFamily: "'Manrope', sans-serif",
}}
>
<App />
</AuthProvider>Available theme keys:
primaryColorprimaryHoverColorradiusfontFamily
AuthLayout usage
import { AuthLayout, LoginForm } from "@rqdhw3n/react-auth-flow";
function LoginPage() {
return (
<AuthLayout
title="Welcome back"
subtitle="Login to continue"
brand={<img src="/logo.svg" alt="Brand" height={32} />}
footer={<a href="/register">Create account</a>}
>
<LoginForm />
</AuthLayout>
);
}Login/Register/Forgot/Reset forms
Included components:
LoginFormRegisterFormForgotPasswordFormResetPasswordFormVerifyEmailForm
Example:
import {
ForgotPasswordForm,
LoginForm,
RegisterForm,
ResetPasswordForm,
VerifyEmailForm,
} from "@rqdhw3n/react-auth-flow";
<LoginForm onSuccess={(user) => console.log("logged in", user)} />;
<RegisterForm onSuccess={(user) => console.log("registered", user)} />;
<ForgotPasswordForm onSuccess={() => console.log("email sent")} />;
<ResetPasswordForm token="reset-token-from-url" />;
<VerifyEmailForm token="email-token" email="[email protected]" />;ProtectedRoute
import { ProtectedRoute } from "@rqdhw3n/react-auth-flow";
<ProtectedRoute
redirectTo="/login"
unauthorizedTo="/forbidden"
roles={["admin"]}
permissions={["users.manage", "billing.edit"]}
requireAllPermissions={true}
fallback={<div>Checking session...</div>}
>
<AdminDashboard />
</ProtectedRoute>;Behavior:
- loading: renders
fallback - not authenticated: redirects to
redirectTo - authenticated but unauthorized: redirects to
unauthorizedToorredirectTo requireAllPermissions={false}switches permission checks to "any match"
GuestRoute
import { GuestRoute } from "@rqdhw3n/react-auth-flow";
<GuestRoute redirectTo="/" fallback={<div>Loading...</div>}>
<LoginPage />
</GuestRoute>;Authenticated users are redirected away from guest-only pages like login and register.
Roles and permissions helpers
useAuth() now exposes:
hasRole(role)hasAnyRole(roles)hasPermission(permission)hasAnyPermission(permissions)hasAllPermissions(permissions)refreshSession()restoreSession()clearError()setError()
import { useAuth } from "@rqdhw3n/react-auth-flow";
function Toolbar() {
const { user, hasRole, hasAnyPermission, logout } = useAuth();
return (
<div>
<span>{user?.name}</span>
{hasRole("admin") && <button>Admin</button>}
{hasAnyPermission(["billing.edit", "users.manage"]) && (
<button>Manage</button>
)}
<button onClick={logout}>Logout</button>
</div>
);
}OTP / 2FA
Included components:
OtpInputTwoFactorForm
Provider support:
- endpoint:
twoFactorVerify, default/auth/2fa/verify - hook method:
verifyTwoFactor({ code })
import { TwoFactorForm } from "@rqdhw3n/react-auth-flow";
<TwoFactorForm
length={6}
title="Enter your code"
subtitle="Check your authenticator app"
onSuccess={(response) => console.log(response)}
/>;Using the hook directly:
import { useAuth } from "@rqdhw3n/react-auth-flow";
function VerifyButton() {
const { verifyTwoFactor } = useAuth();
return (
<button onClick={() => verifyTwoFactor({ code: "123456" })}>
Verify
</button>
);
}SocialLoginButton
UI-only social button component. No OAuth flow is built in.
import { SocialLoginButton } from "@rqdhw3n/react-auth-flow";
<SocialLoginButton provider="google" onClick={() => startGoogleLogin()} />;
<SocialLoginButton provider="github" label="Continue with GitHub" />;
<SocialLoginButton provider="custom" icon={<span>SAML</span>} label="SSO" />;HttpOnly cookie JWT backend example
This package works well with backends that issue cookies instead of exposing tokens to the browser.
// Example request adapter when the backend uses HttpOnly cookies
const requestAdapter = async ({ endpoint, method, data, headers }) => {
const response = await fetch(`https://api.example.com${endpoint}`, {
method,
credentials: "include",
headers: {
"Content-Type": "application/json",
...headers,
},
body: data ? JSON.stringify(data) : undefined,
});
if (!response.ok) {
throw await response.json();
}
return response.status === 204 ? {} : await response.json();
};Expected backend flow:
POST /auth/loginsets the auth cookie and returns{ user }GET /auth/mereturns{ user }POST /auth/logoutclears the cookiePOST /auth/refreshrotates the session and returns{ user }
Laravel example
// routes/api.php
Route::post('/auth/login', [AuthController::class, 'login']);
Route::post('/auth/register', [AuthController::class, 'register']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/auth/me', [AuthController::class, 'me']);
Route::post('/auth/logout', [AuthController::class, 'logout']);
Route::post('/auth/refresh', [AuthController::class, 'refresh']);
Route::post('/auth/2fa/verify', [AuthController::class, 'verifyTwoFactor']);
});
Route::post('/auth/forgot-password', [PasswordController::class, 'forgot']);
Route::post('/auth/reset-password', [PasswordController::class, 'reset']);
Route::post('/auth/verify-email', [VerificationController::class, 'verify']);Typical provider setup with Sanctum:
<AuthProvider baseURL="http://localhost:8000/api">
<App />
</AuthProvider>Express fake backend example
import cookieParser from "cookie-parser";
import express from "express";
const app = express();
app.use(express.json());
app.use(cookieParser());
app.post("/api/auth/login", (_req, res) => {
res.cookie("session", "demo-session", { httpOnly: true, sameSite: "lax" });
res.json({
user: {
id: 1,
name: "Admin Test",
email: "[email protected]",
roles: ["admin"],
permissions: ["users.manage", "billing.edit"],
},
});
});
app.get("/api/auth/me", (req, res) => {
if (!req.cookies.session) {
return res.status(401).json({
error: { code: "UNAUTHENTICATED", message: "Unauthenticated" },
});
}
return res.json({
user: {
id: 1,
name: "Admin Test",
email: "[email protected]",
roles: ["admin"],
permissions: ["users.manage", "billing.edit"],
},
});
});
app.post("/api/auth/logout", (_req, res) => {
res.clearCookie("session");
res.status(204).end();
});
app.post("/api/auth/forgot-password", (_req, res) => {
res.json({ success: true, message: "Reset email queued" });
});
app.post("/api/auth/reset-password", (_req, res) => {
res.json({ success: true, message: "Password updated" });
});
app.post("/api/auth/verify-email", (_req, res) => {
res.json({ success: true, message: "Email verified" });
});
app.post("/api/auth/2fa/verify", (_req, res) => {
res.json({ success: true, message: "2FA verified" });
});
app.listen(8000);Notes
AuthProviderrestores the session on mount by default. Disable withautoRestore={false}.- React 18 and React 19 are supported through peer dependencies.
- React, React DOM, React Router, and
react/jsx-runtimeare externalized from the build. - The published package includes
dist/style.cssand also injects the auth CSS automatically from the package entry.
