Аутентификация в Next.js с использованием iron-session: Client Side Rendering

Аутентификация в Next.js с использованием iron-session: Client Side Rendering

FurryCat 😼

Для реализация аутентификации будем использовать пакет icon-session.

Документация (англ.): https://github.com/vvo/iron-session

Описание проекта

Есть главная страница pages/index, которая получает данные авторизованного пользователя (если он есть) и выводит их. Данные пользователя запрашиваются с роута /api/user, который проверяет, есть ли они в текущей сессии.

Сделаем для простоты аутентификацию на клиенте, то есть компонент страницы сам отправит запрос и выведет лоадер, пока он выполняется.

Есть также форма авторизации - pages/auth - с полями для ввода логина и пароля. Она отправляет данные формы на роут /api/login для аутентификации. Внутри этого роута ищем нужного пользователя (в базе данных) и записываем его данные в сессию, откуда потом их получит роут /api/user.

Таким образом, нам нужно две страницы (profile и auth) и два роута (api/login и api/user). Дополнительно сделаем еще роут api/logout, чтобы можно было разавторизоваться.

Репозиторий проекта: https://github.com/mohnatus/iron-session-demo

Демо: https://iron-session-demo.vercel.app/

Страницы

В страницах нет ничего особенного.

На главной странице запрашиваем данные пользователя и выводим нужную разметку в зависимости от состояния запроса.

pages/index.jsx:

export default function Home() {
  const [loading, setLoading] = useState(true);
  const [user, setUser] = useState(null);

  useEffect() {
    fetch('/api/user')
      .then(res => res.json())
      .then(userData => setUser(userData))
      .finally(() => setLoading(false));
  }

  function logout() {
     fetch('/api/logout')
      .then(res => res.json())
      .then(() => setUser(null));
  }

  if (loading) return <>Loading...</>;
  if (!user) return <>
      Unauthorized access
      <Link href="/auth">Login</Link>
  </>;

  return <>
    Hello, <b>{ user.name }</b>!
    <button onClick={logout}>Logout</button>
  </>;
}

Форма авторизации тоже очень простая.

pages/auth.ts:

export default function Auth() {
  const [login, setLogin] = useState('');
  const [pass, setPass] = useState('');

  function submit(e) {
    e.preventDefault();

    fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({
      login,
    password: pass,
   }),
   headers: {
        Accept: 'application/json',    
        'Content-Type': 'application/json',   
      },
  }

  return <form onSubmit={submit}>
      <input type='text' value={login} 
            onChange={(e) => setLogin(e.target.value)} />
      <input type='password' value={pass} 
            onChange={(e) => setPass(e.target.value)} />
  </form>
}

API-роуты

С API-роутами чуть сложнее. Нам необходимо как-то создать сессию пользователя и получить к ней доступ из роутов.

Для этого нам потребуется утилита withIronSessionApiRoute из пакета iron-session/next. Она оборачивает наш роут и добавляет объекту запроса поле session.

Вторым аргументом утилита принимает объект конфигурации ironOptions.

Обязательные поля:

- password (приватный ключ) - должен содержать не менее 32 символов. Хранить его, конечно, нужно в защищенном виде, но для демо мы его можем просто в переменную положить.

- cookieName - имя куки, где будут храниться данные

Вот так будет выглядеть роут api/login, который находит нужного пользователя в БД и записывает его данные в сессию:

async function loginRoute(req, res) {
   const { login, password } = req.body;
   req.session.user = {
     id: 230,
     name: login 
   };
  await req.session.save();
  res.send({ ok: true });
}
export default withIronSessionApiRoute(loginRoute, {
  password: 'complex_password_at_least_32_characters_log'б
  cookieName: 'myapp_cookiename'
});

А это роут api/user, который достает данные пользователя из сессии:

async function userRoute(req, res) {
  res.send({ user: req.session.user });
}
export default withIronSessionApiRoute(userRoute, {
  password: 'complex_password_at_least_32_characters_log'б
  cookieName: 'myapp_cookiename'
});

И наконец, api/logout:

async function logoutRoute(req, res) {
  res.session.destroy();
  res.send({ ok: true });
}
export default withIronSessionApiRoute(logoutRoute, {
  password: 'complex_password_at_least_32_characters_log'б
  cookieName: 'myapp_cookiename'
});

Вот и вся аутентификация.

Работающее демо можно посмотреть здесь: https://iron-session-demo.vercel.app/

TypeScript

Пришлось немного покопаться, чтобы TypeScript сюда грамотно приделать.

В частности обработчики роутов имеют тип NextApiHandler:

const loginRoute: NextApiHandler = async (req, res) => {}

А чтобы указать, какие данные хранятся в сессии нужно определить интерфейс IronSessionData:

declare module 'iron-session' {
  interface IronSessionData {
    user: {
      id: number;
      name: string;
    }
  }
}


Report Page