Authentication

This section of the guide show on how to implement authentication using the Slyk Api and the Slyk SDK.

Each step has code snippets with examples on how to do each part. Please do note that, for brevity, the examples lack things like error handling.

Sign up

Create the sign up endpoint

First create an API endpoint that uses the Slyk SDK to create a user.

The SDK method is slyk.user.create and it takes the user's email, password, name, and other optional fields. For this example we'll pass verified: true to the method to automatically verify users.

import createSlykClient from '@slyk/slyk-sdk-node';

export default async function handler(request, response) {
  if (request.method !== 'POST') {
    response.status(404);
    return;
  }

  const slyk = createSlykClient({ apikey: process.env.SLYK_API_KEY });
  const { name, email, password } = request.body;
  const user = await slyk.user.create({
    name,
    email,
    password,
    verified: true,
  });
  response.status(200).json(user);
}

Create the sign up page

Then create a page with a form that asks for the user's name, email and password and sends the data to the API endpoint created on the previous step.

import { NextPage } from 'next';
import { useRouter } from 'next/router';
import { useState } from 'react';

const SignUp: NextPage = () => {
  const router = useRouter();
  const [isSubmitting, setSubmitting] = useState(false);

  return (
    <main>
      <h2>Create an account</h2>
      <form
        onSubmit={async event => {
          event.preventDefault();
          setSubmitting(true);
          try {
            const formData = new FormData(event.target as HTMLFormElement);
            const response = await fetch('/api/users', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                name: formData.get('name'),
                email: formData.get('email'),
                password: formData.get('password'),
              }),
            });
            const user = await response.json();
            await router.push('/sign-up/success');
          } finally {
            setSubmitting(false);
          }
        }}
      >
        <label>
          Name
          <input name='name' required type='text' />
        </label>
        <label>
          Email
          <input autoComplete='email' name='email' required type='email' />
        </label>
        <label>
          Password
          <input
            autoComplete='password'
            name='password'
            required
            type='password'
          />
        </label>
        <button type='submit' disabled={isSubmitting}>
          Sign up
        </button>
      </form>
    </main>
  );
};

export default SignUp;

After this step you can either create a success page with a link to the sign in page or you can redirect to the sign in directly.

You can also update the endpoint to create a session using the slyk.auth.login method. You can see how that is done in the sign in flow below.

Sign in

Create the sign in endpoint

Create an API endpoint that takes in the user's email and password and uses the slyk.auth.login method to create a session for the user. The session consists on a JWT used to authorize the user in the API and a refresh token that is used to request a new JWT when the old one expires.

The way you manage and secure these tokens is up to you. You can either store them on the client-side using localStorage or you can store them on secure cookies.

In this example we'll store them on cookies using the cookies module. They'll later be read on API endpoints that need the JWT.

import Cookies from 'cookies';

export default async function handler(request, response) {
  if (request.method !== 'POST') {
    response.status(404);
    return;
  }

  const cookies = new Cookies(request, response);
  const slyk = createSlykClient({ apikey: process.env.SLYK_API_KEY });
  const { email, password } = request.body;
  const { token, refreshToken } = await slyk.auth.login({ email, password });

  cookies.set('token', token, {
    httpOnly: true,
    overwrite: true,
    sameSite: 'lax',
    secure: true,
  });
  cookies.set('refresh-token', refreshToken, {
    httpOnly: true,
    overwrite: true,
    sameSite: 'lax',
    secure: true,
  });

  response.status(204).send(null);
}

If you want, you can update this endpoint to respond with the user's data, by using the slyk.auth.validate method.

Create the sign in page

Create a page with a form that asks for the user's email and password and sends the data to the API endpoint created on the previous step.

import { NextPage } from 'next';
import { useRouter } from 'next/router';
import { useState } from 'react';

const SignIn: NextPage = () => {
  const router = useRouter();
  const [isSubmitting, setSubmitting] = useState(false);

  return (
    <main>
      <h2>Sign in to your account</h2>
      <form
        onSubmit={async event => {
          event.preventDefault();
          setSubmitting(true);
          try {
            const formData = new FormData(event.target as HTMLFormElement);
            const response = await fetch('/api/auth/login', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                email: formData.get('email'),
                password: formData.get('password'),
              }),
            });
            const user = await response.json();
            await router.push('/overview');
          } finally {
            setSubmitting(false);
          }
        }}
      >
        <label>
          Email
          <input autoComplete='email' name='email' required type='email' />
        </label>
        <label>
          Password
          <input
            autoComplete='password'
            name='password'
            required
            type='password'
          />
        </label>
        <button type='submit' disabled={isSubmitting}>
          Sign in
        </button>
      </form>
    </main>
  );
};

export default SignIn;

Get the user's data

Create the user data endpoint

Create an API endpoint that reads the JWT from the cookies (or receives it from the client-side on the request body if you decided to store the tokens on local storage) and uses the slyk.auth.validate method to validate the token and fetch the user's data.

import type { NextApiRequest, NextApiResponse } from 'next';
import createSlykClient from '@slyk/slyk-sdk-node';
import { ServerSession } from 'server/server-session';
import Cookies from 'cookies';
import { User } from 'types/user';
import { ApiError } from 'types/api';

export default async function handler(request, response) {
  if (request.method !== 'POST') {
    response.status(404);
    return;
  }

  const cookies = new Cookies(request, response);
  const token = cookies.get('token');
  const slyk = createSlykClient({ apikey: process.env.SLYK_API_KEY });
  const { user } = await slyk.auth.validate({ token });

  response.status(200).json(user);
}

Refreshing the tokens

Keep in mind that the JWT can expire. If that happens a new one can be requested with the slyk.auth.refresh method, like this:

const refreshToken = cookies.get('refresh-token');
const { token, refreshToken: newRefreshToken } = await slyk.auth.refresh({
  refreshToken,
});

cookies.set('token', token, {
  httpOnly: true,
  overwrite: true,
  sameSite: 'lax',
  secure: true,
});
cookies.set('refresh-token', refreshToken, {
  httpOnly: true,
  overwrite: true,
  sameSite: 'lax',
  secure: true,
});

Make sure to abstract this in some way so that you don't forget to do it.

Sign out

Create the sign out endpoint

Create an API endpoint that reads the refresh token from the cookies (or receives it from the client-side on the request body if you decided to store the tokens on local storage) and uses the slyk.auth.logout method to revoke it. This endpoint should also clear the cookies used to store the tokens.

As we did on the sign in endpoint, we'll use the cookies module to handle cookies.

import createSlykClient from '@slyk/slyk-sdk-node';
import Cookies from 'cookies';

export default async function handler(request, response) {
  if (request.method !== 'POST') {
    response.status(404);
    return;
  }

  const cookies = new Cookies(request, response);
  const slyk = createSlykClient({ apikey: process.env.SLYK_API_KEY });
  const refreshToken = cookies.get('refresh-token');
  await this.client.auth.logout({ refreshToken });

  cookies.set('token', null, {
    httpOnly: true,
    overwrite: true,
    sameSite: 'lax',
    secure: true,
  });
  cookies.set('refresh-token', null, {
    httpOnly: true,
    overwrite: true,
    sameSite: 'lax',
    secure: true,
  });

  response.status(204).send(null);
}

Add a sign out button somewhere in your app

Next add a button to your app that invokes the sign out endpoint and then redirects the user back to the sign in page (or any other non-authenticated page in your app). This button is usually on the profile menu or page.

<button
  type='button'
  onClick={async () => {
    await fetch('/api/auth/logout', { method: 'POST' });
    await router.push('/');
  }}
>
  Sign out
</button>

Last updated