Skip to content

Commit

Permalink
Merge pull request #1627 from dac09/fix/use-fresh-tokens
Browse files Browse the repository at this point in the history
Update auth to refresh tokens when needed
  • Loading branch information
dthyresson authored Jan 26, 2021
2 parents 8e5b123 + 07ee94d commit 2cb612a
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 13 deletions.
21 changes: 12 additions & 9 deletions packages/auth/src/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export interface AuthContextInterface {
/* Determining your current authentication state */
loading: boolean
isAuthenticated: boolean
authToken: string | null
/**
* @deprecated auth tokens are now refreshed when they expire, use getToken() instead. authToken will be removed from this context in future releases
*/
authToken: string | null // @WARN! deprecated, will always be null
/* The current user's data from the `getCurrentUser` function on the api side */
currentUser: null | CurrentUser
/* The user's metadata from the auth provider */
Expand Down Expand Up @@ -55,7 +58,7 @@ export interface AuthContextInterface {
export const AuthContext = React.createContext<AuthContextInterface>({
loading: true,
isAuthenticated: false,
authToken: null,
authToken: null, // @WARN! deprecated, will always be null
userMetadata: null,
currentUser: null,
})
Expand All @@ -69,7 +72,7 @@ type AuthProviderProps = {
type AuthProviderState = {
loading: boolean
isAuthenticated: boolean
authToken: string | null
authToken: string | null // @WARN! deprecated, will always be null
userMetadata: null | Record<string, any>
currentUser: null | CurrentUser
hasError: boolean
Expand All @@ -96,7 +99,7 @@ export class AuthProvider extends React.Component<
state: AuthProviderState = {
loading: true,
isAuthenticated: false,
authToken: null,
authToken: null, // @WARN! deprecated, will always be null
userMetadata: null,
currentUser: null,
hasError: false,
Expand All @@ -115,14 +118,16 @@ export class AuthProvider extends React.Component<
}

getCurrentUser = async (): Promise<Record<string, unknown>> => {
// Always get a fresh token, rather than use the one in state
const token = await this.getToken()
const response = await window.fetch(
`${window.__REDWOOD__API_PROXY_PATH}/graphql`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
'auth-provider': this.rwClient.type,
authorization: `Bearer ${this.state.authToken}`,
authorization: `Bearer ${token}`,
},
body: JSON.stringify({
query:
Expand Down Expand Up @@ -173,15 +178,13 @@ export class AuthProvider extends React.Component<
}

getToken = async () => {
const authToken = await this.rwClient.getToken()
this.setState({ ...this.state, authToken })
return authToken
return this.rwClient.getToken()
}

reauthenticate = async () => {
const notAuthenticatedState: AuthProviderState = {
isAuthenticated: false,
authToken: null,
authToken: null, // @WARN! deprecated, will always be null
currentUser: null,
userMetadata: null,
loading: false,
Expand Down
15 changes: 14 additions & 1 deletion packages/auth/src/__tests__/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require('whatwg-fetch')

import { useEffect, useState } from 'react'

import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import { graphql } from 'msw'
Expand Down Expand Up @@ -41,9 +43,9 @@ const AuthConsumer = () => {
const {
loading,
isAuthenticated,
authToken,
logOut,
logIn,
getToken,
userMetadata,
currentUser,
reauthenticate,
Expand All @@ -52,6 +54,17 @@ const AuthConsumer = () => {
error,
} = useAuth()

const [authToken, setAuthToken] = useState(null)

const retrieveToken = async () => {
const token = await getToken()
setAuthToken(token)
}

useEffect(() => {
retrieveToken()
}, [])

if (loading) {
return <>Loading...</>
}
Expand Down
43 changes: 40 additions & 3 deletions packages/web/src/components/RedwoodApolloProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import {
ApolloProvider,
ApolloClientOptions,
ApolloClient,
ApolloLink,
InMemoryCache,
useQuery,
useMutation,
createHttpLink,
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'

import type { AuthContextInterface } from '@redwoodjs/auth'
import { AuthContextInterface, useAuth } from '@redwoodjs/auth'

import {
FetchConfigProvider,
Expand All @@ -20,12 +23,46 @@ const ApolloProviderWithFetchConfig: React.FunctionComponent<{
config?: Omit<ApolloClientOptions<InMemoryCache>, 'cache'>
}> = ({ config = {}, children }) => {
const { uri, headers } = useFetchConfig()
const { getToken, type: authProviderType, isAuthenticated } = useAuth()

const withToken = setContext(async () => {
if (isAuthenticated && getToken) {
const token = await getToken()

return { token }
}

return { token: null }
})

const authMiddleware = new ApolloLink((operation, forward) => {
const { token } = operation.getContext()

// Only add auth headers when token is present
// Token is null, when !isAuthenticated
const authHeaders = token
? {
'auth-provider': authProviderType,
authorization: `Bearer ${token}`
}
: {}

operation.setContext(() => ({
headers: {
...headers,
// Duped auth headers, because we may remove FetchContext at a later date
...authHeaders,
},
}))
return forward(operation)
})

const httpLink = createHttpLink({ uri })

const client = new ApolloClient({
cache: new InMemoryCache(),
uri,
headers,
...config,
link: ApolloLink.from([withToken, authMiddleware.concat(httpLink)]),
})

return <ApolloProvider client={client}>{children}</ApolloProvider>
Expand Down

0 comments on commit 2cb612a

Please sign in to comment.