Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement auto jwt token refresh before every graphql request with Apollo and React Native?

I would like to implement auto refresh jwt token before every request to GraphQL with Apollo middleware in React Native app. After every user login he gets two tokens: access and refresh. Access token it is the short one for 30-60 min for using in authorization header. And refresh token it is the long one for 60 days for confirm of refresh token graphql mutation. My flow:

  1. User login and gets 2 tokens -> put access token to authorization header with Appollo setContext.
  2. User make request to GraphQL -> check expireTime of accessToken on a client side: -> if it is not expired -> confirm request -> if it is has expired -> call GraphQL refreshToken mutation -> get new tokens -> confirm request. For keeping tokens on the client side i use KeyChain storage. Can you tell me please should i use Apollo cache for keeping tokens too? Should i write Apollo state for tokens? And how i can implement my flow?

GraphQL mutation

mutation UpdateTokens($refreshToken: String!, $refreshTokenId: String!) 
 {
    updateTokens(refreshToken: $refreshToken, refreshTokenId: $refreshTokenId) {
      user {
        name
        phone
      }
      accessToken
      refreshToken
    }
  }

App.js

import React from 'react'
import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link'
import { ApolloProvider } from 'react-apollo'
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
import * as Keychain from 'react-native-keychain'
import AppNavigator from './AppNavigator'

const httpLink = createHttpLink({
  uri: 'http://localhost:4000'
})

const cache = new InMemoryCache()

const authLink = setContext(async (req, { headers, ...context }) => {
  const tokens = await Keychain.getGenericPassword()
  const accessToken = tokens.username
  return {
    headers: {
      ...headers,
      authorization: accessToken ? `Bearer ${accessToken}` : ''
    },
    ...context
  }
})

const client = new ApolloClient({
  link: ApolloLink.from([authLink, httpLink]),
  cache,
  connectToDevTools: true
})

const App = () => {
  return (
    <ApolloProvider client={client}>
      <ApolloHooksProvider client={client}>
        <AppNavigator />
      </ApolloHooksProvider>
    </ApolloProvider>
  )
}

export default App
like image 638
jocoders Avatar asked Aug 07 '19 11:08

jocoders


Video Answer


1 Answers

I honestly think you are on the right path -- as I read your required functionality I thought of apollo-link-context and was happy to see you were taking that approach. We recently had to implement similar functionality in a react-native app, which entailed attaching custom headers with authentication-related data. To retrieve this data we were required to make an async request, although ours was over the network to a third-party service. We did this all in setContext from the client just as you are. This worked well.

I do not think you need to concern yourself with updating your Apollo cache with your tokens, manually or otherwise, for at least a few reasons. First, given the quasi-sensitive nature of what you are storing, it would be best practice to defer to the more secure storage solution, which in this case is likely the Keychain. In addition, having a single source of truth for the tokens in your application can keep things clean.

Assuming you do not want to write this data to cache, I would double-check Apollo client is not automatically doing so. For example, if you for some reason previously queried your token data, Apollo might automatically update your cache upon receiving the mutation payload. Just something to be mindful of.

like image 181
Greg Brodzik Avatar answered Oct 13 '22 20:10

Greg Brodzik