Session management

In this section, you learn about session management in your client application. There are several ways to manage sessions in single-page web applications. The most common one is to manage sessions through browser cookies. In this section, you learn how to manage sessions with browser cookies.

Install the js-cookie dependency in your project by running the following command in your terminal.

npm i js-cookie

Open the components/Login.js file and add the following code changes. You use the js-cookie library to store the login response in the browser cookies. You can retrieve this cookie and the secret inside it whenever needed and query Fauna.

// components/Login.js

import { useState, useEffect } from 'react'
import { useMutation, gql } from '@apollo/client'
import Cookie from 'js-cookie';
import { useRouter } from 'next/router'

...

export default function Login() {
  const [loginFunc, { data, loading, error }] = useMutation(LOGIN)
  const router = useRouter()
    
  const [state, setState] = useState({
    email: '',
    password: ''
  })

  useEffect(() => {
    if(data) {
    Cookie.set(
      'fauna-session', 
      JSON.stringify(data.login),
      { expires: data.ttl }
    )
    router.push('/')
  }
    }, [data, router])
    
    const doLogin = e => {
        e.preventDefault();
        Cookie.remove('fauna-session')
        loginFunc({
            variables: {
                ...state
            }
        }).catch(e => console.log(e))   
    }

...
}

After you make the changes your components/Login.js file should be simmilar to the following code snippet.

// components/Login.js

import { useState, useEffect } from 'react'
import { useMutation, gql } from '@apollo/client'
import { useRouter } from 'next/router'
import Cookie from 'js-cookie';


const LOGIN = gql`
  mutation OwnerLogin($email: String!, $password: String! ) {
    login(email: $email, password: $password) {
        ttl
        secret
        email
    }
  }
`;

export default function Login() {
  const [loginFunc, { data, loading, error }] = useMutation(LOGIN)
  const router = useRouter()
    
  const [state, setState] = useState({
    email: '',
    password: ''
  })

  useEffect(() => {
    if(data) {
      Cookie.set(
        'fauna-session', 
        JSON.stringify(data.login),
        { expires: data.ttl } // 30 mins from now
      )
      router.push('/')
    }
  }, [data, router])
    
  const doLogin = e => {
    e.preventDefault();
    Cookie.remove('fauna-session')
    loginFunc({
      variables: {
          ...state
      }
    }).catch(e => console.log(e))   
  }

  const handleChange = (e) => {
    setState({
      ...state,
      [e.target.name]: e.target.value
    })
  }

  if (loading) return 'Submitting...';

    return (
      <div uk-grid="true">
        <div>
          <div className="uk-card uk-card-default uk-card-body">
            <h3 className="uk-card-title">Login</h3>
            {error ? 
              <div className="uk-alert-danger" uk-alert style={{ maxWidth: '300px', padding: '10px'}}>
                Incorrect email and password
              </div> : null 
            }
            <form onSubmit={doLogin}>
              <div className="uk-margin">
                <input 
                  className="uk-input" 
                  type="text" 
                  placeholder="Email" 
                  name="email"
                  onChange={handleChange}
                  value={state.email}
                />
              </div>
              <div className="uk-margin">
                <input 
                  className="uk-input" 
                  type="password" 
                  placeholder="Password" 
                  name="password"
                  onChange={handleChange}
                  value={state.password}
                />
              </div>
              <div className="uk-margin">
                <input className="uk-input" type="submit" />
              </div>
            </form>
          </div>
        </div>
      </div>
    )
}

Create a new component components/Dashboard. Authenticated users are presented with a dashboard view. If a user is not authenticated that user is redirected to the login view. Add the following code to your Dashboard component.

// components/Dashboard.js

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import Cookie from 'js-cookie'

export default function Dashboard() {
  const router = useRouter()
  const cookies = Cookie.get('fauna-session');

  useEffect(() => {
    if(!cookies) {
      router.push('/login')
    } 
  }, [cookies, router])

  return <div>Dashboard</div>
}

Replace the contents of your index page with the following code.

// pages/index.js
import Dashboard from '../components/Dashboard'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <Dashboard />
    </div>
  )
}

When a new user signs up you want to clear the session cookies as well. Add the following code changes to your SignUp component.

// components/Signup.js

import { useState, useEffect } from 'react'
import { useMutation, gql } from '@apollo/client'
import Cookie from 'js-cookie'


export default function Signup() {
  ...
  const doSignup = e => {
    e.preventDefault(); 
    Cookie.remove('fauna-session')
    signupUserFunc({
      variables: {
        ...state,
      },
    })
  }
  ...
  return (
      ...
  )
}

Once you make the changes your final components/Signup.js file will look like following.

import { useState, useEffect } from 'react'
import { useMutation, gql } from '@apollo/client'
import Cookie from 'js-cookie'

const SIGN_UP = gql`
  mutation OwnerSignUp($email: String!, $name: String!, $password: String! ) {
    registerOwner(email: $email, name: $name, password: $password) {
      _id
      name
      email
    }
  }
`;

const INITAL_STATE = {
  name: '',
  email: '',
  password: '',
}

export default function Signup() {
  const [signupUserFunc, { data, loading, error }] = useMutation(SIGN_UP);

  const [state, setState] = useState(INITAL_STATE);

  useEffect(() => {
    if(data) {
      alert('Signup Complete')
      setState(INITAL_STATE);
      console.log(data);
    }
  }, [data])

  const handleChange = e => {
    setState({
      ...state,
      [e.target.name]: e.target.value
    })
  }

  const doSignup = e => {
    e.preventDefault(); 
    Cookie.remove('fauna-session')
    signupUserFunc({
      variables: {
          ...state,
      },
    })
  }

  if (loading) return 'Submitting...';
  if (error) return 'Something went wrong...'

  return (
    <div uk-grid>
      <div>
        <div className="uk-card uk-card-default uk-card-body">
          <h3 className="uk-card-title">Sign up</h3>
          <form onSubmit={doSignup}>
            <div className="uk-margin">
              <input 
                className="uk-input" 
                type="text"
                placeholder="Username" 
                name="name" 
                onChange={handleChange} 
                value={state.name}
                autoComplete="off"
              />
            </div>
            <div className="uk-margin">
              <input 
                className="uk-input" 
                type="text" 
                placeholder="Email" 
                name="email"
                onChange={handleChange}
                value={state.email}
              />
            </div>
            <div className="uk-margin">
              <input 
                className="uk-input" 
                type="password" 
                placeholder="Password" 
                name="password"
                onChange={handleChange}
                value={state.password}
              />
            </div>
              <div className="uk-margin">
                <input className="uk-input" type="submit" />
              </div>
          </form>
        </div>
      </div>
    </div>
  )
}

You can take advantage of the apollo-client library’s setContext function to dynamically update the authorization token for every GraphQL request when cookies update. Make the following changes in your apollo-client.js file.

// apollo-client.js

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import Cookie from 'js-cookie';

const httpLink = createHttpLink({
    uri: 'https://graphql.fauna.com/graphql',
});

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const cookies = Cookie.get('fauna-session');
  const token = cookies ? JSON.parse(cookies).secret : process.env.NEXT_PUBLIC_FAUNA_SECRET
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : ''
    }
  };
});

const client = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
});

export default client;

Run your application with npm run dev command and sign in with a user you have registered. Observe your browser cookies. Notice that a cookie value named fauna-session is saved.

session from cookies

In the next section, you learn how to retrieve the user access token from your cookies and make GraphQL queries and mutations using it.