Assuming you have a login api at api.example.com/v1/login that expects some credentials
to be sent in exchange for a jwt token. This api will return the token and an additional
fields authExpiresIn that will inform the client for how many seconds the token will be valid.
In out example the response looks like
{
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibm...",
authExpiresIn: 900,
}Note that
tokenExpirationreturned fromdoSignInmay be anumberin which case is interpreted as seconds of validity left, or aDateobject in which case it is interpreted as the instant of expiration. It is also possible to omit this field in which case the token will not be expired automatically.
The simplest example uses an AppRoutes component which is responsible to change the routing
structure according to the authentication status
function AppRoutes() {
const isAuth = useIsAuthenticated();
return (
<Routes>
<Route path='/open' element={<PublicPage />} />
{!isAuth ? (
<>
<Route path='/login' element={<LoginPage />} />
<Route path='*' element={<Navigate to={'/login'} replace />} />
</>
) : (
<>
<Route path='/' element={<HomePage />} />
<Route path='*' element={<Navigate to={'/'} replace />} />
</>
)}
</Routes>
)
}Then the easiest solution is to wrap the application with AuthProvider and configure it
properly. In our case we provide doSignIn which is an async function responsible to
exchange credentials with our server, and onTokenChange function which will update the
Authorization header used by axios.
onTokenChangeis a handy function, as it will be called on log-in, log-out, renew, after token expiration and on application load (if a valid token has been retrieved from the storage), therefore all the setup that needs to be done with the token shall be done here.
const AUTH_URL = `api.example.com/v1`
export default function App() {
return (
<BrowserRouter>
<AuthProvider
doSignIn={async (ctx, credentials) => {
const res = (await axios.post(`${AUTH_URL}/login`, credentials))
return {
token: res.authToken,
tokenExpiration: res.authExpiresIn,
}
}}
onTokenChange={(ctx, token) => {
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
} else {
delete axios.defaults.headers.common['Authorization']
}
}}
>
<AppRoutes/>
</AuthProvider>
</BrowserRouter>
)
}If your system also expects a log-out procedure to be called it can be done by providing
doSignOut.
Please note that this function fill only be called when the respective
signOutfunction is called on theuseAuthhook and not when a token expires or is deleted from the storage by any other mean. This function can be used to request a premature expiration of the session but should not in any case replace an automated session expiration on the server.
export default function App() {
return (
<BrowserRouter>
<AuthProvider
...
doSignOut={async (ctx) => {
// Call the server if a log-out api exists
await axios.post(`${AUTH_URL}/logout`)
}}
>
<AppRoutes/>
</AuthProvider>
</BrowserRouter>
)
}If your system supports token renewal a slight modification to doSignIn should be made
and doRenew async function should be implemented. Whenever doSignIn returns a renewal
token, this will be stored and upon token expiration the system will call doRenew to
attempt to renew the token.
export default function App() {
return (
<BrowserRouter>
<AuthProvider
doSignIn={async (ctx, credentials) => {
const res = (await axios.post(`${AUTH_URL}/login`, credentials))
return {
token: res.authToken,
tokenExpiration: res.authExpiresIn,
renew: res.renewToken,
renewExpiration: res.renewExpiresIn,
}
}}
doRenew={async (ctx) => {
const res = (await axios.post(`${AUTH_URL}/renew`, { token: ctx.renew?.token }))
return {
token: res.authToken,
tokenExpiration: res.authExpiresIn,
}
}}
>
<AppRoutes/>
</AuthProvider>
</BrowserRouter>
)
}Once setup you can initiate a login from anywhere in your application by mean of useAuth()
hook.
export function LoginPage() {
const { signIn } = useAuth()
function doLogin(event) {
signIn({
username: event.target.username.value,
password: event.target.password.value,
}) // This will be sent to doSignIn as the second parameter
}
return (
<form onSubmit={doLogin}>
...
</form>
)
}And to log out just
export function LogOutButton() {
const { signOut } = useAuth()
return (
<button onClick={signOut}>Log Me Out</button>
)
}