Framework-agnostic authentication for React apps: session management, automatic token refresh, an authenticated HTTP client, and reusable auth UI — behind a small integration surface. No backend assumptions, no business logic.
npm install @ferrumec/authreact-router-dom is an optional peer dependency, only needed if you use
@ferrumec/auth/router.
import {
AuthProvider,
JwtProvider,
LocalStorageTokenStorage,
useAuth,
LoginPage,
AuthGuard,
} from "@ferrumec/auth";
import "@ferrumec/auth/theme.css"; // optional — CSS custom properties, not required
const provider = new JwtProvider({
baseUrl: "/api/auth",
storage: new LocalStorageTokenStorage(),
idleTimeoutMs: 30 * 60_000, // optional — log out after 30 min idle
});
function Root() {
return (
<AuthProvider provider={provider} apiBaseUrl="/api">
<App />
</AuthProvider>
);
}
function App() {
const auth = useAuth();
if (!auth.authenticated) return <LoginPage />;
return (
<AuthGuard>
<Dashboard />
</AuthGuard>
);
}
// Elsewhere in the app — use auth.client instead of fetch()
function Dashboard() {
const auth = useAuth();
useEffect(() => {
auth.client.request("/orders").then(setOrders);
}, []);
// ...
}JwtProvider (and any other provider implementing RegistrableAuthProvider)
supports self-service sign-up: creating the account starts a session
immediately, same as login(), and a follow-up step confirms the address
with a code the backend emails out.
import { RegisterPage } from "@ferrumec/auth";
<RegisterPage
title="Create your account"
onSuccess={() => navigate("/")}
onSwitchToLogin={() => navigate("/login")}
/>;RegisterPage is a two-step wizard — username/email/password, then a
confirmation-code screen — built from useAuth() plus the standalone
RegisterForm and EmailConfirmationForm components, exactly the way
LoginPage is built from LoginForm. useAuth().register /
requestEmailConfirmation / confirmEmailConfirmation are only defined when
the configured provider supports them; redirect-flow providers (OAuth2/OIDC,
Keycloak, Auth0, Azure) leave them undefined since those identity providers
own sign-up themselves.
const auth = useAuth();
await auth.register?.({ username, password }); // starts a session, like login()
const { nonce } = await auth.requestEmailConfirmation?.(email) ?? {};
await auth.confirmEmailConfirmation?.(nonce, codeFromEmail);By default this targets POST {baseUrl}/auth/register to create the
account, then POST {baseUrl}/notifications/console/preferences/set /
.../confirm to request and redeem the confirmation code (configurable via
emailConfirmationChannel / emailConfirmationSubject on JwtProvider, or
override endpoints.register for the registration call itself). Skip the
confirmation step with <RegisterPage skipEmailConfirmation /> if your
backend doesn't require it.
Every provider implements the same AuthProvider interface — swap the
constructor, change nothing else in your app:
// First-party JSON/JWT backend
new JwtProvider({ baseUrl: "/api/auth", storage: new LocalStorageTokenStorage() });
// Generic OAuth2 / OIDC (Authorization Code + PKCE)
new OAuthProvider({
endpoints: { authorize: "...", token: "...", userInfo: "..." },
clientId: "...",
redirectUri: window.location.origin + "/callback",
storage: new LocalStorageTokenStorage(),
});
// Keycloak
new KeycloakProvider({ baseUrl: "https://id.example.com", realm: "acme", clientId: "...", redirectUri: "...", storage });
// Auth0
new Auth0Provider({ domain: "acme.us.auth0.com", clientId: "...", redirectUri: "...", storage });
// Azure AD / Entra ID
new AzureProvider({ tenantId: "...", clientId: "...", redirectUri: "...", storage });For redirect-flow providers (OAuth2/OIDC family), call auth.login() from a
button click — it navigates the browser to the identity provider. The
resulting ?code= is exchanged automatically the next time AuthProvider
mounts and calls initialize(), so make sure your redirect URI renders the
app with <AuthProvider> at the root.
- Owns: tokens, refresh scheduling and deduplication, idle timeout, cross-tab logout/refresh sync, request auth injection/retry, auth UI.
- Does not own: authorization (roles/permissions/scopes — keep that in
your app), routing (only
@ferrumec/auth/routertouchesreact-router-dom, and it's optional), business logic, styling (Tailwind utility classes ship on every element alongside a stablefka-*class you can target instead).
AuthProvider accepts loadingScreenComponent and
sessionExpiredDialogComponent overrides for the two pieces it renders
automatically. LoginPage and UserMenu are plain compositions of
useAuth() — write your own and never import ours.
src/
api/ AuthClient, HttpClient, SessionManager, TokenStorage
components/ LoginPage, LoginForm, RegisterPage, RegisterForm,
EmailConfirmationForm, LogoutButton, UserMenu, LoadingScreen,
AuthGuard, SessionExpiredDialog
context/ AuthProvider, AuthContext
hooks/ useAuth, useSession
providers/ BaseAuthProvider, JwtProvider, OAuthProvider,
KeycloakProvider, Auth0Provider, AzureProvider
router/ AuthRoutes (ProtectedRoute, PublicOnlyRoute) — optional
types/ the AuthProvider / TokenStorage contracts
utils/ EventEmitter, BroadcastBus, PKCE helpers