@pawells/react-auth
v1.0.5
Published
Keycloak Authorization Code Flow with PKCE authentication for React SPAs
Maintainers
Readme
@pawells/react-auth
Keycloak OIDC authentication for React applications. Built on oidc-client-ts with Authorization Code + PKCE flow, automatic silent token renewal, popup login support, axios interceptor, and Apollo Client link.
Installation
yarn add @pawells/react-auth oidc-client-tsQuick start
Wrap your application (inside your router) with AuthProvider and access auth state via useAuth.
import { AuthProvider, useAuth } from '@pawells/react-auth';
function AppWithAuth() {
const navigate = useNavigate();
return (
<AuthProvider
authority="https://keycloak.example.com/realms/my-realm"
client_id="my-spa-client"
redirect_uri={`${window.location.origin}/auth/callback`}
post_logout_redirect_uri={window.location.origin}
onSigninCallback={() => navigate('/')}
>
<App />
</AuthProvider>
);
}
function NavBar() {
const { isAuthenticated, isLoading, user, login, logout } = useAuth();
if (isLoading) return <Spinner />;
if (!isAuthenticated) return <button onClick={login}>Sign in</button>;
return (
<>
<span>{user?.profile.name}</span>
<button onClick={logout}>Sign out</button>
</>
);
}API reference
AuthProvider
Provider component that supplies Keycloak OIDC authentication to the component tree. Place it inside your router so onSigninCallback can call useNavigate.
| Prop | Type | Required | Description |
|---|---|---|---|
| authority | string | Yes | Keycloak realm base URL, e.g. https://host/realms/my-realm |
| client_id | string | Yes | Public (non-confidential) Keycloak client ID |
| redirect_uri | string | Yes | URI to redirect to after login — must be registered in Keycloak |
| post_logout_redirect_uri | string | No | URI to redirect to after logout |
| popup_redirect_uri | string | No | URI for popup login callback — enables loginWithPopup() |
| scope | string | No | OAuth2 scopes. Defaults to 'openid profile email' |
| storageType | 'sessionStorage' \| 'localStorage' | No | Token storage strategy. Defaults to 'sessionStorage' |
| onSigninCallback | (user: User) => void | No | Called after a successful signin redirect callback |
All other UserManagerSettings from oidc-client-ts are accepted and forwarded.
useAuth
Returns the AuthContextValue from the nearest AuthProvider. Throws if called outside a provider.
const {
isAuthenticated, // boolean — true when user has a valid, non-expired token
isLoading, // boolean — true during initial hydration, callback processing, or silent renew
user, // User | null — the OIDC user object
error, // Error | null — most recent auth error
login, // () => Promise<void> — initiates redirect login
loginWithPopup, // () => Promise<void> — opens a popup login window
logout, // () => Promise<void> — redirects to Keycloak logout
getAccessToken, // () => Promise<string | null> — returns a valid token, renewing silently if needed
clearSession, // () => Promise<void> — removes the user from storage without redirecting
} = useAuth();useAuthAxios
Returns an Axios instance with a request interceptor that automatically attaches a valid Bearer token to every outgoing request. Silent token renewal is triggered transparently when the current token is expired.
import { useAuthAxios } from '@pawells/react-auth';
function DataService() {
const api = useAuthAxios(); // new axios instance
const fetchItems = () => api.get('/api/items');
}Pass an existing Axios instance to attach the interceptor to it instead:
const sharedAxios = axios.create({ baseURL: 'https://api.example.com' });
function DataService() {
const api = useAuthAxios(sharedAxios);
}Note: if multiple components call
useAuthAxioswith the same shared instance, multiple interceptors will be registered. Use a dedicated instance per hook invocation to avoid this.
createAuthApolloLink
Creates an Apollo Client ApolloLink that prepends a valid Bearer token to the Authorization header of every GraphQL operation. Silent token renewal is triggered transparently.
import { createAuthApolloLink, useAuth } from '@pawells/react-auth';
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client';
function ApolloSetup({ children }: { children: React.ReactNode }) {
const { getAccessToken } = useAuth();
const client = useMemo(() => {
const authLink = createAuthApolloLink(getAccessToken);
const httpLink = new HttpLink({ uri: '/graphql' });
return new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache() });
}, [getAccessToken]);
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}KeycloakAuthConfig
Full configuration interface accepted by AuthProvider (all props listed in the AuthProvider table above, plus any UserManagerSettings fields).
Token utilities
import { parseJwt, isTokenExpired } from '@pawells/react-auth';
// Decode a JWT payload without signature verification (client-side use only)
const payload = parseJwt<{ sub: string; email: string }>(token);
// Check if a JWT is expired (clockSkewSeconds adds a grace window)
const expired = isTokenExpired(token, 30); // true if expired more than 30s agoNotes
- PKCE (S256) is enabled by default via
oidc-client-ts— no additional configuration is required. - Silent token renewal via refresh token is enabled automatically. The
getAccessTokenmethod anduseAuthAxiosinterceptor both renew tokens transparently. storageTypeis only read on initial render. Changes to this prop after mount are ignored because theUserManagerinstance is created once and retains its original storage.- React Strict Mode safe —
AuthProviderguards against double-invocation of the signin redirect callback in development. - Popup login requires
popup_redirect_urito be configured onAuthProviderand registered in Keycloak's "Valid redirect URIs". If the popup is blocked by the browser,loginWithPopup()throws — callers should catch and fall back tologin()if desired.
