Skip to main content

Adding authentication

This step consists in adding an authentication system to the application

onekijs provides an authentication library offering:

  • several authentication methods (form based, oauth2, external login, ...)
  • the restriction of access to pages to authenticated users only (can be based on roles).
  • the provision of a security context accessible everywhere in the application. The security context contains data identifying the connected user (username, roles, ...).

Final result

The result of this step is as follows:

New in this step

The shopping cart page (accessible via the checkout button) is now only for authenticated users.
If you are not yet authenticated, you are redirected to the login page where you can log in.

Securing the cart page

note

onekijs provides several hooks to handle authentication
In this tutorial, we will use these hooks:

  • useLogin returns a service handling the login phase. In our case, this is sending a POST request to /auth/login containing the username and password.
  • useLogout returns a service managing the disconnection phase. In our case, sending a GET request to /auth/logout
  • useSecurityContext returns an object identifying the disconnected user.

To prevent an unauthenticated user from accessing the shopping cart page, you can use the HOC secure (to learn more about the HOC, click here).

getting-started/cra//step04-authentication/src/pages/cart.tsx
loading...

By default, an unauthenticated user is redirected to /login.
However, this path can be configured via a global configuration introduced below.

Adding a global configuration

onekijs recommends to place the configuration settings in a central location. Usually the configuration is placed in the file src/settings.ts.

Some components of the framework use the content of this file to configure themselves. This is the case for the secure HOC.
To redirect the user to /auth instead of /login, create the file src/settings.ts with the following content:

src/settings.ts
export default {
routes: {
// redirect to /auth if a non authenticated user
// tries to access a secured page
login: '/auth',
},
} as AppSettings;

and pass the configuration to <App/>
getting-started/cra/step04-authentication/src/index.tsx
loading...

Adding the login page

The login page displays a very basic form containing two fields: username and password
The page uses the useLogin hook to handle the login phase which consists of:

  • sending a POST ajax request containing the username / password to a server
  • process the response by creating a security context and storing it in the global state. The content of the security context is the content of the response.

useLogin also uses the global configuration src/settings.ts to know:

  • the type of authentication (form based, external, Oauth2, ...)
  • the URL to use to send the POST request

Let's first update the content of src/settings.ts

src/settings.ts
export default {
routes: {
login: '/login',
},
idp: {
default: {
// We want to use a form based authentication
type: 'form',
// The URL to send the POST request containing
// username / password
loginEndpoint: '/auth/login',
// URL to retrieve the security context
// This URL is called to verify if the user
// is already authenticated or not
userinfoEndpoint: '/userinfo',
},
},
} as AppSettings;

note

The endpoints /auth/login, /auth/logout and /userinfo are mocked to simulate a backend server.
The mocked server is defined in src/__server__

You can check the documentation of the library msw to learn more about mocking a server inside a browser

Then create the auth page:
info

The auth page uses the hook useForm which is explained later in the step Adding form

getting-started/cra/step04-authentication/src/pages/login.tsx
loading...

Create a route to associate the <LoginPage/> to /login
src/pages/_router.tsx
const RootRouter = (): JSX.Element => {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<AppLayout />}>
<Route path="/products/*" element={<ProductsRouter />} />
<Route path="/cart" element={<CartPage />} />
</Route>
<Route index element={<Navigate to="/products" replace />} />
</Routes>
);
};

export default RootRouter;

Updating the Navbar to show the logged in user

Now we want to show the username of the logged in user in the navigation bar.
We can retrieve the username anywhere in the application via the useSecurityContext hook

The content of the security context is the response sent by the backend when the userinfo endpoint is called.
In our case, the backend returns a very simple object containing only the username:

{
"username": "john.doe"
}

To display the username in the Navbar, update the <Navbar/> component:
getting-started/cra//step04-authentication/src/modules/core/components/Navbar.tsx
loading...

Adding the logout page

We want to provide the user with a link to log out.
The logout action calls the backend server to delete the cookie and clean up the security context from the global state.

This action is handled by the useLogout hook. This hook gets the logout endpoint from src/settings.ts

src/settings.ts
export default {
routes: {
login: '/login',
},
idp: {
default: {
type: 'form',
loginEndpoint: '/auth/login',
logoutEndpoint: '/auth/logout',
userinfoEndpoint: '/userinfo',
},
},
} as AppSettings;

Create a logout page to handle the logout process.
By default, the useLogout hook sends a GET request to the backend server

getting-started/cra/step04-authentication/src/pages/logout.tsx
loading...

Update the router to take into account this new page

src/pages/_router.tsx
const RootRouter = (): JSX.Element => {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/logout" element={<LogoutPage />} />
<Route element={<AppLayout />}>
<Route path="/products/*" element={<ProductsRouter />} />
<Route path="/cart" element={<CartPage />} />
</Route>
<Route index element={<Navigate to="/products" replace />} />
</Routes>
);
};

export default RootRouter;
src/modules/core/components/Navbar.tsx
const Navbar: FC = () => {
const [loggedUser] = useSecurityContext('username');
return (
<div className="app-top-bar">
<Link href="/">
<h1>My Store</h1>
</Link>
<div className="app-top-bar-right">
{loggedUser && (
<div className="user">
{loggedUser}{' '}
<Link className="logout" href="/logout">
[Log out]
</Link>
</div>
)}
<Link href="/cart" className="button fancy-button">
<i className="material-icons">shopping_cart</i>
Checkout
</Link>
</div>
</div>
);
};

export default Navbar;

Next step

Now that we can identify the logged-in user, we can save the cart contents in the cloud so we don't lose its contents after a refresh
In the next step, we introduce the services offered by onekijs to retrieve and send data via AJAX requests.