This is the closest I’ve gotten regarding setting up my own auth system. I have golang graphql server I’m connecting to and a nextjs + apollo + redux-persist client that I’m starting to piece together slowly. Currently, I’m trying to pass the access token that is persisted in my store and refresh the tokens if necessary. However, I noticed that upon the refreshing of my tokens, it’s storing my session in the database twice, every time. Not when I’m authenticating on the /login page.
const time = Math.round(Date.now() / 1000)
async function getRefreshedAccessToken() {
try {
const authState = store.getState().auth
const { data } = await clientAuth.mutate({
mutation: REFRESH,
variables: {
input: {
refreshToken: authState.refresh
}
}
})
if (data && data.refreshTokens) {
authActions.authUser(data.refreshTokens)
return data.refreshTokens.access
}
} catch (error) {
authActions.logoutUser()
console.log(error)
return null
}
}
export function getAccessToken() {
const authState = store.getState().auth
const accessExp = authState.payload.exp <= time
const refreshExp = authState.refreshExpires <= time
if (
authState &&
authState.access &&
authState.payload) {
if (time + 1 * 60 <= authState.payload.exp) {
if (time + 3 * 60 >= authState.payload.exp) {
getRefreshedAccessToken()
}
} else if (accessExp == true && refreshExp == false) {
getRefreshedAccessToken()
} else if (accessExp == true && refreshExp == true) {
authActions.logoutUser()
}
return authState.access
} else {
return null
}
}
Which gets used here:
export const linkTokenHeader = setContext(async (_, { headers }) => {
var accessToken = getAccessToken()
return {
headers: {
...headers,
Authorization: accessToken ? `${accessToken}` : ''
}
}
})
export const clientAuth = new ApolloClient({
connectToDevTools: true,
link: linkAuth,
cache: new InMemoryCache()
})
const options = {
watchQuery: {
fetchPolicy: 'cache-and-network',
errorPolicy: 'none'
},
query: {
fetchPolicy: 'cache-and-network',
errorPolicy: 'none'
},
mutate: {
errorPolicy: 'none'
}
}
export const clientMain = new ApolloClient({
connectToDevTools: true,
defaultOptions: options,
link: ApolloLink.from([linkError, linkTokenHeader, linkMain]),
cache: new InMemoryCache()
})
As my _app.js looks like:
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ApolloProvider client={clientMain}>
<Component {...pageProps} />
</ApolloProvider>
</PersistGate>
</Provider>
)
}
Then I have this hook:
export function useIsAuth() {
const isAuth = useSelector(state => state.auth.payload)
const syncTabLogout = event => {
if (event.key === 'isAuth' && event.newValue === 'false') authActions.logoutUser()
}
useEffect(() => {
window.addEventListener('storage', syncTabLogout)
return () => {
window.removeEventListener('storage', syncTabLogout)
}
}, [])
useEffect(() => {
if (!isAuth) {
clientAuth.clearStore()
clientMain.clearStore()
localStorage.setItem('isAuth', false)
} else {
localStorage.setItem('isAuth', true)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAuth])
return isAuth
}
For the following page:
const PRIVATE = gql`
query Private {
privateInfo
}
`
function Test() {
const isAuth = useIsAuth()
const { data } = useQuery(PRIVATE, { skip: !isAuth });
const globalState = useSelector((state) => state)
return (
<>
<pre>
{JSON.stringify(globalState, null, 2)}
</pre>
{isAuth ? (
<>
{data ? data.privateInfo : 'unauthorized'}
<button onClick={() => authActions.logoutUser()}>
Logout
</button>
</>
) : (
<Link href={"/login"}>
<button>
Authenticate
</button>
</Link>
)}
</>
);
}
I’m trying to figure out how I can appropriately go about this. Secondly, I would appreciate any comments about using GQL for my auth server and how I’m going about my client. I do not want to move forward until I better understand my current position.