Documents
authentication
authentication
Type
External
Status
Published
Created
Mar 5, 2026
Updated
Mar 5, 2026

import NotV5 from '/docs/snippets/_not-updated-to-v5.md'

Examples cookbook: Authentication flow with JWT#

This page is part of the back end customization examples cookbook. Please ensure you've read its introduction.

💭 Context:

Out of the box, the front-end website of does not provide any log in functionality. Logging in is done by accessing Strapi's admin panel at .

Let's add a basic login page to the front-end, -powered website included in the /client folder of FoodAdvisor. The login page will be accessible at and contain a typical email/password login form. This will allow programmatically authenticating API requests sent to Strapi.

Example login page A possible example of a login form on the front-end website of FoodAdvisor

🎯 Goal:

Create a front-end component to:

  1. to display a login form,
  2. send a request to the /auth/local route of the Strapi back-end server to authenticate,
  3. get a (JWT),
  4. and store the JWT into the property of your browser for later retrieval and authentication of our requests.

Additional information about JWT authentication can be found in the Users & Permissions plugin documentation.

🧑‍💻 Code example:

To achieve this, in the /client folder of the project, you could create a pages/auth/login.js file that contains the following example code. Highlighted lines show the request sent to the /auth/local route provided by Strapi's Users & Permissions plugin:

This file uses the formik package - install it using yarn add formik and restart the dev server.


import React from 'react';
import { useFormik } from 'formik';
import { Button, Input } from '@nextui-org/react';
import Layout from '@/components/layout';
import { getStrapiURL } from '@/utils';

const Login = () => {
  const { handleSubmit, handleChange } = useFormik({
    initialValues: {
      identifier: '',
      password: '',
    },
    onSubmit: async (values) => {
      /**
       * API URLs in Strapi are by default prefixed with /api,
       * but because the API prefix can be configured
       * with the rest.prefix property in the config/api.js file,
       * we use the getStrapiURL() method to build the proper full auth URL.
       **/
      const res = await fetch(getStrapiURL('/auth/local'), {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(values),
      });
      /**
       * Gets the JWT from the server response
       */
      const { jwt } = await res.json();
      /**
       * Stores the JWT in the localStorage of the browser. 
       * A better implementation would be to do this with an authentication context provider
       * or something more sophisticated, but it's not the purpose of this tutorial.
       */
      localStorage.setItem('token', jwt); 
    },
  });
  /**
   * The following code renders a basic login form 
   * accessible from the localhost:3000/auth/login page.
   */
  return (
    <Layout>
      <div className="h-full w-full flex justify-center items-center my-24">
        <form onSubmit={handleSubmit} className="flex flex-col gap-y-6 w-4/12 ">
          <h1 className="font-bold text-3xl mb-6">Login</h1>
          <Input
            onChange={handleChange}
            type="email"
            name="identifier"
            label="Email"
            placeholder="Enter your email"
          />
          <Input
            type="password"
            name="password"
            label="Password"
            placeholder="Enter your password"
            onChange={handleChange}
          />
          <Button type="submit" className="bg-primary rounded-md text-muted">
            Login
          </Button>
        </form>
      </div>
    </Layout>
  );
};

export default Login;

Enhanced Authentication with Session Management#

The above example uses the traditional JWT approach. For enhanced security, you can enable session management mode in your Users & Permissions configuration, which provides shorter-lived access tokens and refresh token functionality.

Configuration#

First, enable session management in your /config/plugins.js:

module.exports = ({ env }) => ({
  'users-permissions': {
    config: {
      jwtManagement: 'refresh',
      sessions: {
        accessTokenLifespan: 604800, // 1 week (default)
        maxRefreshTokenLifespan: 2592000, // 30 days
        idleRefreshTokenLifespan: 604800, // 7 days
      },
    },
  },
});

Enhanced Login Component#

Here's an updated login component that handles both JWT and refresh tokens:

import React, { useState } from 'react';
import { useFormik } from 'formik';
import { Button, Input } from '@nextui-org/react';
import Layout from '@/components/layout';
import { getStrapiURL } from '@/utils';

const EnhancedLogin = () => {
  const [isLoading, setIsLoading] = useState(false);

  const { handleSubmit, handleChange } = useFormik({
    initialValues: {
      identifier: '',
      password: '',
    },
    onSubmit: async (values) => {
      setIsLoading(true);
      try {
        const res = await fetch(getStrapiURL('/auth/local'), {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(values),
        });

        const data = await res.json();

        if (res.ok) {
          // Store both tokens (session management mode)
          if (data.refreshToken) {
            localStorage.setItem('accessToken', data.jwt);
            localStorage.setItem('refreshToken', data.refreshToken);
          } else {
            // Legacy mode - single JWT
            localStorage.setItem('token', data.jwt);
          }

          // Redirect to protected area
          window.location.href = '/dashboard';
        } else {
          console.error('Login failed:', data.error);
        }
      } catch (error) {
        console.error('Login error:', error);
      } finally {
        setIsLoading(false);
      }
    },
  });

  return (
    <Layout>
      <div className="h-full w-full flex justify-center items-center my-24">
        <form onSubmit={handleSubmit} className="flex flex-col gap-y-6 w-4/12">
          <h1 className="font-bold text-3xl mb-6">Enhanced Login</h1>
          <Input
            onChange={handleChange}
            type="email"
            name="identifier"
            label="Email"
            placeholder="Enter your email"
          />
          <Input
            type="password"
            name="password"
            label="Password"
            placeholder="Enter your password"
            onChange={handleChange}
          />
          <Button
            type="submit"
            className="bg-primary rounded-md text-muted"
            disabled={isLoading}
            {isLoading ? 'Logging in...' : 'Login'}
          </Button>
        </form>
      </div>
    </Layout>
  );
};

export default EnhancedLogin;

<br />

:::strapi What's next?
Learn more about how custom [services and controllers](/cms/backend-customization/examples/services-and-controllers) can help you tweak a Strapi-based application.
:::