@glinr/theauth-react
v0.5.0
Published
React hooks for TheAuth auth (with v0.5 session rotation)
Readme
@glinr/theauth-react
React provider and hooks for TheAuth authentication.
Install
npm install @glinr/@glinr/theauth-reactUsage
Wrap your app with KavachProvider, then use hooks anywhere in the tree.
import { KavachProvider, useSession, useUser, useSignIn, useSignOut } from '@glinr/theauth-react';
function App() {
return (
<KavachProvider apiUrl="https://auth.yourapp.com" tenantId="your-tenant-id">
<Dashboard />
</KavachProvider>
);
}
function Dashboard() {
const { session, isLoading } = useSession();
const { user } = useUser();
const { signIn } = useSignIn();
const { signOut } = useSignOut();
if (isLoading) return <p>Loading...</p>;
if (!session) return <button onClick={() => signIn({ email, password })}>Sign in</button>;
return (
<div>
<p>Welcome, {user?.email}</p>
<button onClick={signOut}>Sign out</button>
</div>
);
}Exports
KavachProvider: context provider, wrap your app rootuseSession: current session and loading stateuseUser: authenticated user objectuseSignIn: sign-in actionuseSignOut: sign-out actionuseSignUp: sign-up actionuseAgents: manage AI agents for the current useruseRotateSession: trigger / observe access-token rotation (v0.5+)useKavachContext: raw context access
External mode with rotation (v0.5+)
When TheAuth sits behind another auth API (Java/Go/Python), you can run the
provider in external mode. Pass an external config object instead of
relying on the local managed flow. Set refreshPath to opt into the rotation
loop — proactive refresh, exponential-backoff retries, online/offline
recovery, and reuse-detection callbacks all come along for the ride.
import { KavachProvider, useRotateSession } from "@glinr/theauth-react";
function App() {
return (
<KavachProvider
external={{
apiUrl: "https://api.example.com",
mePath: "/api/auth/me",
loginPath: "/auth/github",
logoutPath: "/auth/logout",
// ── v0.5 rotation ──────────────────────────────────────
refreshPath: "/auth/refresh",
proactiveRefreshLeadMs: 120_000, // rotate 2 min before expiry
retry: {
maxRetries: 3,
initialDelayMs: 1_000,
backoffMultiplier: 2,
maxDelayMs: 10_000,
requestTimeoutMs: 15_000,
},
onAuthError: (code) => {
// Auth-class failures: send the user back to login.
if (code === "token_reuse" || code === "family_revoked") {
window.location.href = "/login?reason=security";
return;
}
window.location.href = "/login";
},
onSessionRotated: () => {
// Refetch authenticated queries with whatever client you use.
// Apollo example (framework-agnostic — adapt for TanStack Query, urql, etc.):
// apolloClient.refetchQueries({ include: "active" });
},
}}
>
<Dashboard />
</KavachProvider>
);
}
function RotateButton() {
const { rotate, status, isOnline } = useRotateSession();
return (
<button onClick={() => rotate()} disabled={!isOnline || status === "rotating"}>
{status === "rotating" ? "Refreshing…" : "Refresh session"}
</button>
);
}How rotation behaves
- Single-flight. Concurrent
rotate()calls join one in-flight request. - Proactive. When the access token's expiry is known, a timer fires
proactiveRefreshLeadMsbefore it lapses. On mount, if the cached expiry is already inside the lead window, rotation runs immediately. SetproactiveRefreshLeadMs: 0to disable proactive rotation entirely (manualrotate()calls still work). - Retry policy. Network failures and
5xxresponses are retried with exponential backoff (defaults: 1s → 2s → 4s, capped at 10s, 15s timeout per attempt).401responses are not retried — they fireonAuthError. - Offline-aware. While
navigator.onLineis false, rotations short-circuit and the nextonlineevent triggers one rotation if anything was queued. Otherwise the existing schedule resumes against the known expiry. - SSR-safe. All browser APIs are guarded; rotation is a no-op on the server.
Backend contract
The endpoint at refreshPath is expected to:
- Accept
POSTwithcredentials: "include"(httpOnly refresh-token cookie). - Return 200 with a JSON body
{ "accessTokenExpiresAt": "<ISO8601>" }on success. New cookies (refresh + access) should be set viaSet-Cookie. - Return 401 with
{ "error": "<code>" }on failure, where<code>is one of:token_missing,token_not_found,token_expired,token_reuse,family_revoked,absolute_timeout. UseerrorCodeMapif your backend emits different strings.
The @glinr/theauth package's createSessionRefresher() handler implements
this contract verbatim.
Migrating from a hand-rolled refresh loop
If you have a bespoke refresher (e.g. token-refresh-service.ts):
- Delete the in-app retry/queue/timer code.
- Add
refreshPathandonAuthErrorto your<KavachProvider external>config. - Optional: pass
onSessionRotatedto refetch your data layer. - Replace direct
triggerRefresh()calls withuseRotateSession().rotate().
Docs
License
MIT
