Authenticating Users in React
The backend API is ready to be used. Now let's add authentication in the frontend side.
Here is how the React application is organized:
- When clicking the Log in or Log out button, the application calls the functions defined in
requests/auth.ts
to make requests to the server. - Information about the current user is stored in the root component
App.tsx
under the namecurrentUser
. If the user has logged in, this state is of type{ id: number, name: string }
. Otherwise, its value isnull
. - When logging in, the server returns information about the user which is used to set the
currentUser
state. On logout, the application sets this state tonull
.
Knowing, on the client side, if a user is logged in and who they are is useful to manage the display of user interface elements. This allows us, for example, to know which navigation buttons should be visible.
Open the file requests/auth.ts
and implement the empty functions.
import axios from 'axios';
import { ICredentials, IUser } from '../interfaces';
export async function logIn(credentials: ICredentials): Promise<IUser> {
const response = await axios.post<IUser>('/api/auth/login', credentials);
return response.data;
}
export async function logOut(): Promise<void> {
await axios.post('/api/auth/logout');
}
export async function signUp(credentials: ICredentials): Promise<IUser> {
const response = await axios.post<IUser>('/api/auth/signup', credentials);
return response.data;
}
Go to http://localhost:3000/login and log in. You should be redirected to the home page. If you click on the Profile button in the navigation bar, you should be taken to your personal page. You can add or remove stories if you wish.
Now let's refresh the page. You are redirected to the login page as if you were logged out. Ouch!
The reason behind this is that the front-end application no longer knows that you are logged in. If you look at the App
component, you will see that the currentUser
state is initialized to null
when the application is loaded. So we need to find a way to keep track of the user's login state even if the page is refreshed.
To do this, you will use an additional cookie to store this information that will be readable by the front-end application.
Open the api.controller.ts
file and add the userCookie
option.
import { Context } from '@foal/core';
// ...
@UseSessions({
cookie: true,
user: fetchUser(User),
userCookie: (ctx: Context<User|undefined>) => ctx.user ? JSON.stringify({ id: ctx.user.id, name: ctx.user.name }) : '',
})
This option sets an additional user
cookie on the client host with information about the current user.
Now go back to the App.tsx
file and add the getCurrentUserFromCookie
function below.
import * as cookie from 'cookie';
function getCurrentUserFromCookie(): IUser | null {
const userCookie = cookie.parse(document.cookie).user as string|undefined;
if (!userCookie) {
return null;
}
try {
return JSON.parse(userCookie);
} catch (error: any) {
return null;
}
}
export default function App() {
const [currentUser, setCurrentUser] = useState(getCurrentUserFromCookie());
// ...
}
When the application loads, this function will check if a user
cookie exists with information about the current user. If so, its contents will be used to set the value of currentUser
.
Refresh the page. The application now works as expected.
You could also have set a client-side cookie in the
logIn
function and deleted it in thelogOut
function. But this solution does not work well when the user is automatically logged out after a period of inactivity (session expiration).