Deal with `HttpOnly` cookies with Redux+GraphQL
Tags: graphql, auth
Published: 2022-02-24 11:45:24

---
title: Deal with `HttpOnly` cookies with Redux+GraphQL
published: true
description: 
tags: graphql,auth
series: graphql
cover_image: https://res.cloudinary.com/practicaldev/image/fetch/s--RlqwldxD--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehd9wcdwrvrn9bu5g4z5.jpg
---

# My idea

In this article [Use GraphQL to log in with React and Apollo / GraphQL with `HttpOnly` cookie](https://dev.to/peterlitszo/use-graphql-to-log-in-with-react-and-apollo-4n3f#graphql-with-raw-httponly-endraw-cookie), I talk about how to deal with GraphQL when we try to sign in.

Now I have no idea how to sign out... I have no idea to ask server send response with `Remove-Cookie`. There is `Set-Cookie` rather than `Remove-Cookie`. By the way, do not tell user sign out if they are offline is also fucking stupid thing.

So the idea is:

* Ask server send response with `HttpOnly` cookie.
* If OK, set a `isLogged` flag to be `"true"` in LocalStorage.
* If we want to logged out, just set `isLogged` flag to be `"false"`.

In my opinion, it is the most easy way to deal with `HttpOnly` cookie --- Just do not deal with those.

# With Redux

The state of authorization should be a global state rather than a private state of one component. So I will use Redux to handle that data.

We need create a slice named `auth` (I am going to use GraphQL to get data. It is not hard!):

```JavaScript
// Redux's toolkit.
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// GraphQL client.
import { useLazyQuery, gql } from '@apollo/client';

// Import store's root's state's type.
import type { RootState } from './store';
// Import typed function (It works better with TypeScript).
import { useDispatch } from '../hooks';

// The slice's name and the key of the localStorage
const SLICE_NAME = 'auth';
const IS_LOGGED_ITEM_KEY = `store-${SLICE_NAME}-isLogged`;
```

Now we need add type for the slice's state. It is the union of four substates:

```TypeScript
export enum AuthStateEnum {
  LoggedWithInfo,
  LoggedWithoutInfo,
  LoggedAndLoadingInfo,
  Unlogged,
}

interface LoggedWithInfo {
  state: AuthStateEnum.LoggedWithInfo;
  name: string;
  email: string;
  isAdmin: boolean;
}

interface LoggedWithoutInfo {
  state: AuthStateEnum.LoggedWithoutInfo;
}

interface LoggedAndLoadingInfo {
  state: AuthStateEnum.LoggedAndLoadingInfo;
}

interface Unlogged {
  state: AuthStateEnum.Unlogged;
}

type AuthState =
  | LoggedWithInfo
  | LoggedWithoutInfo
  | LoggedAndLoadingInfo
  | Unlogged;
```

At first, a user is `Unlogged` if it is the first time for he/she to enter the website. After he/she sign up (and sign in), localStorage will have a item, which means that the user is logged, but not information about the user. The state will be `LoggedWithoutInfo`. If the application find the user is `LoggedWithoutInfo`, it will send request to server and set the state to `LoggedAndLoading`. If finally application get the data, the state will change to `LoggedWithInfo`.

Then the user enter the website tomorrow, and no data still here for he/she but localStorage and `HttpOnly` cookies, the application will set his/her state to  `LoggedWithoutInfo` and try to send request to get user's information.

So it will get the initial state by this statement:

```TypeScript
const initialState = ((): AuthState => {
  // localStorage will store if client store the HttpOnly cookie `JWT` to get
  // the authentication. If it is is true, we will try to connect to server to
  // get user's information. (So it will be loading soon)
  const isLogged = !!localStorage.getItem(IS_LOGGED_ITEM_KEY);

  return isLogged
    ? {
        state: AuthStateEnum.LoggedWithoutInfo,
      }
    : {
        state: AuthStateEnum.Unlogged,
      };
})();
```

```TypeScript
export const authSlice = createSlice({
  name: SLICE_NAME,
  initialState,
  reducers: {
    // Sign out, just remove the flag.
    signOut: (state: AuthState) => {
      localStorage.removeItem(IS_LOGGED_ITEM_KEY);
      state.state = AuthStateEnum.Unlogged;
    },
    // Really signed, and set the current user's data
    // into store.
    signInWithInfo: (
      _state: AuthState,
      actions: PayloadAction<Omit<LoggedWithInfo, 'state'>>,
    ) => {
      localStorage.setItem(IS_LOGGED_ITEM_KEY, 'true');
      return {
        state: AuthStateEnum.LoggedWithInfo,
        ...actions.payload,
      } as LoggedWithInfo;
    },
    // Just change the state to `signInAndLoading`.
    signInAndLoading: (state: AuthState) => {
      localStorage.removeItem(IS_LOGGED_ITEM_KEY);
      state.state = AuthStateEnum.LoggedAndLoadingInfo;
    },
    // Just change the state to `signInWithoutInfo`.
    signInWithoutInfo: (state: AuthState) => {
      localStorage.removeItem(IS_LOGGED_ITEM_KEY);
      state.state = AuthStateEnum.LoggedWithoutInfo;
    },
  },
});
```

Now we need use apollo's GraphQL query hook to sign in. It is hard to use hook in `createAsyncThunk`(Do you have good idea? Please email me), so I create a hook to deal with. Here is the GraphQL query statement.

```TypeScript
const SIGN_IN_QUERY = gql`
  query SignIn($email: String!, $password: String!) {
    # *************************************************************************
    # WARING: Remember to change interface SIGN_IN_QUERY_Return if you change
    # the code below.
    # *************************************************************************

    # Input email, password, and no output.
    user(email: $email, password: $password) {
      name
      email
      isAdmin
    }
  }
`;

interface SIGN_IN_QUERY_Return {
  name: string;
  email: string;
  isAdmin: boolean;
}
```

And the hook to sign in is:

```TypeScript
export const useSignIn = () => {
  const [signIn, { data, error, loading }] = useLazyQuery(SIGN_IN_QUERY);
  const dispatch = useDispatch();

  if (data) {
    dispatch(signInWithInfo(data as SIGN_IN_QUERY_Return));
  } else if (loading) {
    dispatch(signInAndLoading());
  } else if (error) {
    // do nothing... now
    // TODO: Add error message for user.
  }

  return (email: string, password: string) =>
    signIn({ variables: {email, password} });
};
```