0

I have been trying to implement react server-side-rendering using next, and redux-observable, now i want to implement auth

On signin

  • click signin
    • dispatch signin
      • set signin type
      • set signin data
    • call backend api auth/signin
      • if the response says that token is expired
        • call backed api auth/refresh using refreshToken
        • set cookie based on auth/refresh response token
        • set auth data based on auth/refresh response
      • else
        • set cookie based on auth/signin response token
        • set auth data based on auth/signin response

On accessing pages that needs auth

  • check for cookies called token
    • if exists
      • call backed api auth/me to authorize
      • if the response says that token is expired
        • call backed api auth/refresh using refreshToken
        • set cookie based on auth/refresh response token
        • set auth data based on auth/refresh
      • else
        • set auth data based on auth/me response
    • else
      • redirect to signin

Steps above happens inside the epics, as follows

/epics/signin.js

export const signinEpic = (action$, store) => action$
  .ofType(SIGNIN)
  .mergeMap(() => {
    const params = { ... }
    return ajax(params)
      .concatMap((response) => {
        const { name, refreshToken } = response.body
        if (refreshToken && name === 'TokenExpiredError') {
          const refreshParams = { ... }
          return ajax(refreshParams)
            .concatMap((refreshResponse) => {
              setToken(refreshResponse.body.auth.token)
              const me = { ... }
              return [
                authSetMe(me),
                signinSuccess(),
              ]
            })
            .catch(error => of(signinFailure(error)))
        }
        const me = { ... }
        setToken(response.body.auth.token)
        return [
          authSetMe(me),
          signinSuccess(),
        ]
      })
      .catch(error => of(signinFailure(error)))
  })

I did some console.log(Cookies.get('token')) to ensure that the cookie gets saved, and it prints the token just fine, saying that its there, but when i checked under browser console > Application > Cookies, nothing is there

So in auth epic below, the getToken() will always return '' which will always dispatch authMeFailure(error)

/epics/auth.js

// this epic will run on pages that requires auth by dispatching `authMe()`
export const authMeEpic = action$ => action$
  .ofType(AUTH_ME)
  .mergeMap(() => {
    const params = {
      ...,
      data: {
        ...
        Authorization: getToken() ? getToken() : '', // this will always return ''
      },
    }
    return ajax(params)
      .mergeMap((response) => {
        const { name, refreshToken } = response.body
        if (refreshToken && name === 'TokenExpiredError') {
          const refreshParams = { ... }
          return ajax(refreshParams)
            .mergeMap((refreshResponse) => {
              setToken(refreshResponse.body.auth.token)
              const me = { ... }
              return authMeSuccess(me)
            })
            .catch(error => of(authMeFailure(error)))
        }
        const me = { ... }
        setToken(response.body.auth.token)
        return authMeSuccess(me)
      })
      .catch(error => of(authMeFailure(error)))
  })

I use js-cookie for getting and setting cookies

EDIT: i actually prepared an auth lib containing getToken, setToken and removeToken, as follows

import Cookies from 'js-cookie'

export const isAuthenticated = () => {
  const token = Cookies.get('token')
  return !!token
}

export const getToken = () => Cookies.get('token')

export const setToken = token => Cookies.set('token', token)

export const removeToken = () => Cookies.remove('token')

and yes, i could have just used the setToken() on the epics, was just trying to directly test the cookie set method

UPDATE:

  • it seems that despite its not being in Console > Application > Cookies, its exists on every pages as it's printing the correct token if i do console.log(getToken()) inside the component render method
  • But every time i refresh the page, its gone. Kind of like it is being stored in a redux state, which is weird

UPDATE #2:

ok i think i manage to make it work, it turns out that we need 2 types of cookie, server side (the one's generated on refresh) and a client side (persist on navigating), so the reason that i wasn't able to get the token on epics its because it was not passed from the server side (at least this is my understanding)

8
  • Can you try to delete { path: '/' } from the Cookies.set? Just to make sure it's not an issue with the path. Commented Mar 20, 2018 at 11:07
  • wow! you're right, its the path, i am so ashamed of myself right now, thanks btw! Commented Mar 20, 2018 at 14:03
  • No, i am sorry its still not working, don't know how it seems like its working previously Commented Mar 20, 2018 at 14:09
  • Try to remove the {path} options in all Cookies.set Also could you please provide the code of getToken() ? Commented Mar 20, 2018 at 14:31
  • i did remove all {path} options, btw question updated with lib/auth.js Commented Mar 20, 2018 at 19:39

1 Answer 1

0

Inspired by this issue comment on github

yarn add cookie-parser

on ./server.js (you need to have a custom server to be able to do this)

const cookieParser = require('cookie-parser')

...

server.use(cookieParser())

on ./pages/_document.js

export default class extends Document {
  static async getInitialProps(...args) {
    // ...args in your case would probably be req
    const token = args[0].req ? getServerToken(args[0].req) : getToken()

    return {
      ...
      token,
    }
  }

  render() {
    ...
  }
}

on ./lib/auth.js or on any place you put your token methods

export const getServerToken = (req) => {
  const { token = '' } = req.cookies

  return token
}

export const getToken = () => {
  return Cookies.get('token') ? Cookies.get('token') : ''
}

I am not 100% understand how this is solving my problem, but i am gonna leave it like this for now

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.