@vyro-x/react-auth
v2.0.0
Published
Client authentication
Keywords
Readme
@vyro-x/react-auth
Client-side authentication for a React app
Migrating to v2
v2 changes how the access token is read. Access tokens now live ~10 minutes (down from 1 hour) and there is no periodic background refresh — the token is refreshed on demand, at the moment a request needs it.
Every getAccessToken() call site must become await getValidAccessToken().
- import { getAccessToken } from '@vyro-x/react-auth/lib/service/tokens'
+ import { getValidAccessToken } from '@vyro-x/react-auth/lib/service/refresh'
- const token = getAccessToken()
+ const token = await getValidAccessToken()or via the service barrel:
- authService.tokens.getAccessToken()
+ await authService.getValidAccessToken()The enclosing function must become async. See
Use access token in an API request
for the axios-interceptor and Apollo-link versions.
getAccessToken() still exists and still returns the stored token, so your
app keeps compiling — but it is deprecated, logs a warning, and does not
check whether the token is still valid. With the shorter TTL it will
routinely hand back an expired token and your requests will start failing
with 401 / invalid-jwt. It will be removed in the next major.
Also changed in v2:
- The refresh token is server-owned (httpOnly cookie).
getRefreshToken()andsetRefreshToken()are gone;clearRefreshToken()remains for cleaning up the legacy JS-readable cookie. AuthProviderno longer refreshes on a timer. It still refreshes once on mount, so wrapping your app in it is still required.
Usage
Install
npm i @vyro-x/react-authAuthProvider
Wrap your app with the AuthProvider
import { AuthProvider, setConfig as setAuthConfig } from '@vyro-x/react-auth';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { App } from './App';
const container = document.getElementById('root');
if (!container) throw new Error('Failed to find the root element');
const root = ReactDOM.createRoot(container);
// It's important to call this early, before the AuthProvider.
setAuthConfig({
authFrontendUrl: process.env.REACT_APP_AUTH_FRONTEND_URL,
authApiUrl: process.env.REACT_APP_AUTH_API_URL,
});
root.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>,
);Callback URL
Add a callback URL at path=/auth/verify. This url is responsible for verifying and completing the auth flow.
import { useVerifyCallback } from '@vyro-x/react-auth';
import { useNavigate } from 'react-router';
export default function VerifyPage() {
const navigate = useNavigate();
useVerifyCallback({
// This is up to you on how to handle a fatal authentication error
onError: () => navigate('/error/500'),
});
return <>Loading...</>;
}Config
Define config vars. Typically these are set in .env
REACT_APP_AUTH_FRONTEND_URL=https://127.0.0.1:8002
REACT_APP_AUTH_API_URL=https://127.0.0.1:3006For the Vyro dev env these are:
REACT_APP_AUTH_FRONTEND_URL=https://login.vyrolabs.net
REACT_APP_AUTH_API_URL=https://authentication-api.vyrolabs.netAnd in production they are:
REACT_APP_AUTH_FRONTEND_URL=https://login.vyro.com.au
REACT_APP_AUTH_API_URL=https://authentication-api.vyro.com.auAdd a login button
import { authService } from '@vyro-x/react-auth';
// ...
<button onClick={() => authService.login()}>Login</button>;Add a logout button
import { authService } from '@vyro-x/react-auth';
// ...
<button onClick={() => authService.logout()}>Logout</button>;Use auth state
import { useAuth } from '@vyro-x/react-auth';
// ...
const {
// Boolean
isAuthenticated,
// User ID in GraphQL API
userId,
// Usage hasRole('admin') => boolean
hasRole,
// Array of roles
roles,
// Authentication state is initiatialising. Don't rely on the above until this is true.
isLoading,
} = useAuth();Protecting routes
import { Outlet, Route, Routes } from 'react-router-dom';
import { authService, useAuth } from '@vyro-x/react-auth';
import VerifyPage from './pages/auth/verify';
import ProtectedPage from './pages/protected';
const ProtectedRoutes = () => {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return null;
}
if (!isAuthenticated) {
authService.login();
return null;
}
return <Outlet />;
};
export const RoutesTree = () => {
return (
<Routes>
<Route element={<ProtectedRoutes />}>
<Route path={'/'} element={<HomePage />} />
// This path will require the user to be logged in
<Route path={'/i-am-protected'} element={<ProtectedPage />} />
</Route>
// This is your callback URL. Make sure it is public
<Route path={'/auth/verify'} element={<VerifyPage />} />
// You probably want this
<Route path={'/error/500'} element={<Error500Page />} />
</Routes>
);
};Use access token in an API request
getValidAccessToken() is the only supported way to read the access token.
It returns the current token, first refreshing it if it is expired or about
to expire, so callers never send a stale token. It is async — await it.
Concurrent callers share a single refresh request.
import { authService } from '@vyro-x/react-auth';
// ...
axios.request({
headers: {
Authorization: `Bearer ${await authService.getValidAccessToken()}`,
},
});For an axios instance, do it once in an async request interceptor:
client.interceptors.request.use(async (config) => {
const accessToken = await authService.getValidAccessToken();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});For Apollo/Hasura, use an async setContext link (and async
connectionParams for websocket subscriptions):
import { setContext } from '@apollo/client/link/context';
const accessTokenLink = setContext(async (_, { headers }) => {
const accessToken = await authService.getValidAccessToken();
return accessToken
? { headers: { ...headers, authorization: `Bearer ${accessToken}` } }
: { headers };
});