@authu/react
React SDK for AuthU - Centralized Multi-Tenant Authentication Service.
Installation
npm install @authu/react
pnpm add @authu/react
yarn add @authu/react
Usage
1. Configure the Provider
Wrap your app with AuthUProvider:
import {AuthUProvider} from '@authu/react';
function App() {
return (
<AuthUProvider
domain="https://auth.example.com"
clientId="your-client-id"
redirectUri={window.location.origin + '/callback'}
>
<YourApp />
</AuthUProvider>
);
}
2. Use the Hook
Access authentication state and methods with useAuthU:
import {useAuthU} from '@authu/react';
function Profile() {
const {isAuthenticated, isLoading, user, login, logout} = useAuthU();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) {
return <button onClick={() => login()}>Log in</button>;
}
return (
<div>
<p>Welcome, {user?.name}</p>
<button onClick={() => logout()}>Log out</button>
</div>
);
}
3. Protect Routes
Use PrivateRoute to protect authenticated routes. It automatically triggers the OAuth login flow when the user is not authenticated:
import {PrivateRoute} from '@authu/react';
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
</Routes>
);
}
PrivateRoute Props
children | ReactNode | - | Content to render when authenticated |
fallback | ReactNode | null | Content to show while loading or redirecting |
loginOnUnauthenticated | boolean | true | Auto-trigger login when not authenticated |
Automatic Login Flow
When loginOnUnauthenticated is true (default), visiting a protected route triggers:
PrivateRoute checks isAuthenticated
- If not authenticated → calls
login() automatically
- User is redirected to AuthU login page
- After login, AuthU redirects back to the app
AuthUProvider exchanges the code for tokens
- User is now authenticated and sees the protected content
This enables seamless SSO: users logged into AuthU are automatically authenticated in the app without extra clicks.
4. Get Access Token for API Calls
Use useApiToken to get tokens for authenticated API requests:
import {useApiToken} from '@authu/react';
function ApiComponent() {
const {getToken} = useApiToken();
const fetchData = async () => {
const token = await getToken();
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
};
return <button onClick={fetchData}>Fetch Data</button>;
}
5. Sign In Another Device with a QR Code
DeviceLinkQrCode shows a QR code that signs the scanning phone into the
current account, with no password:
import {DeviceLinkQrCode} from '@authu/react';
function OpenOnMobile() {
return <DeviceLinkQrCode size={220} />;
}
The code encodes a one-shot AuthU link that expires after two minutes, and the
component renews it on its own while mounted. Opening the link gives the phone
its own AuthU session cookie, then redirects to the tenant homeUrl, where the
silent renewal signs the app in. Since anyone photographing the screen within
that window gets the session, only mount the component when the user asks for
it.
For a custom rendering, useDeviceLink() returns {link, isLoading, error, renew} with the same auto-renewal, and createDeviceLink() on useAuthU()
creates a single link.
Token Storage
Tokens never touch localStorage, sessionStorage or any other persistent
store. The access token and the refresh token live in memory for the lifetime
of the page: an XSS on the host application can still act as the user while the
page is open, but it can no longer walk away with a 30-day refresh token and
replay it from anywhere, later.
The trade-off is that a reload loses the tokens. The session itself is not
lost, though: it lives in AuthU's own cookie, and the SDK re-derives the tokens
from it on every page load with one silent round trip.
Silent renewal (prompt=none)
On mount, when no code is coming back in the URL, the SDK redirects to
/authorize?prompt=none. AuthU answers without ever showing a page:
- session cookie still valid → it redirects straight back with a fresh
authorization code, which the SDK exchanges for tokens. The visitor sees a
single redirect and stays signed in.
- no session → it redirects back with
error=login_required. The SDK reads
that as "anonymous", renders accordingly, and records the attempt in
sessionStorage so the tab never loops through AuthU again.
The URL the visitor asked for is saved before the round trip and restored
afterwards, so landing on redirectUri is invisible.
Set silentAuth: false to opt out. The session then does not survive a reload
at all: every load starts signed out until login() is called.
Logging out
logout() now navigates to AuthU's /logout rather than only clearing memory.
It has to: the session lives in the AuthU cookie, so a purely local logout
would let the next silent renewal sign the visitor right back in. The browser
returns to redirectUri, or to options.returnTo when given - which must be a
URI registered for the tenant, or on the tenant's home origin, otherwise AuthU
falls back to its own host.
Upgrading from 1.0.x
Two behaviour changes to expect in an app that upgrades:
- any token left in
localStorage under authu_tokens by a previous version
is deleted on the first load;
logout() performs a full navigation. Code that expected it to be
synchronous and stay on the page must move whatever it did afterwards into
options.returnTo.
API Reference
AuthUProvider Props
domain | string | Yes | AuthU server URL |
clientId | string | Yes | OAuth2 client ID |
redirectUri | string | Yes | Callback URL after login |
scope | string | No | OAuth2 scopes (default: openid profile email) |
audience | string | No | API audience for access tokens |
silentAuth | boolean | No | Silent session renewal (default: true, see below) |
useAuthU Returns
isLoading | boolean | True while checking auth state |
isAuthenticated | boolean | True if user is logged in |
user | AuthUUser | null | User profile info |
error | Error | null | Auth error if any |
login(options?) | function | Redirect to login |
logout(options?) | function | Log out user |
getAccessToken() | function | Get current access token |
createDeviceLink() | function | One-shot sign-in link for another device ({url, expiresAt} | null) |
Development
Build
pnpm run build
Lint
pnpm run lint
Publishing
Prerequisites
- Be logged in to npm:
npm login
- Have publish rights on
@authu scope
Publish a New Version
- Update version in
package.json
- Build and publish:
pnpm run build
pnpm publish --access public
The --access public flag is required for scoped packages.
Changelog
1.0.15
- Feature: Added
loginOnUnauthenticated prop to PrivateRoute for automatic login triggering
- Feature: Added
fallback prop to PrivateRoute for custom loading states
- Docs: Improved documentation for automatic SSO flow
1.0.10
- Fix: Changed PKCE storage from
sessionStorage to localStorage to prevent state/code_verifier loss during cross-domain OAuth redirects
1.0.9
License
MIT