Reputation: 605
Currently, when I authenticate a user with firebase, I store their auth token in localStorage
to be used later to connect to my backend like so:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization token to the headers
const token = localStorage.getItem(AUTH_TOKEN) || null
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
return forward(operation)
})
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: authMiddleware.concat(authAfterware).concat(httpLink)
})
}
My problem with this is that I have no way to refresh the token once it expires. So I tried to use the following to set the authorization token for apollo:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const asyncAuthLink = setContext(
() => {
return new Promise((success, reject) => {
firebase.auth().currentUser.getToken().then(token => {
success({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
}).catch(error => {
reject(error)
})
})
}
)
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: asyncAuthLink.concat(authAfterware.concat(httpLink))
})
}
This works when the user first authenticates, but once the user refreshes the page, firebase is no longer initialized when my graphql queries are sent to my backend, so the token is not sent with it. Is there a way I can asynchronously wait for firebase.auth().currentUser
so this will work? Or is there another approach I should take entirely? As far as I know (100% sure) currentUser.getIdToken
only makes a network call if the current token is no longer valid. I think this is acceptable as in cases where the token is not valid, the backend can't respond anyway, so I will need to wait for a token refresh to continue.
Some other ideas I thought of:
localStorage
to store the auth token, refresh it in authAfterware
if my backend sends a 401 response back and retry the request.Thanks!
Upvotes: 5
Views: 648
Reputation: 26
I know is a bit late but I was stuck on that as well and found a way to solve it. Maybe is not the best one but at least it works. My approach is to create a Next api endpoint to retrieve the user token using the getUserFromCookies method:
import { NextApiRequest, NextApiResponse } from "next";
import { getUserFromCookies } from "next-firebase-auth";
import initAuth from "../../utils/initAuth";
initAuth();
const handler = async (req: NextApiRequest, res: NextApiResponse<any>) => {
try {
const user = await getUserFromCookies({ req, includeToken: true });
const accessToken = await user.getIdToken();
return res.status(200).json({ success: true, accessToken });
} catch (e) {
console.log(`${e}`);
return res.status(500).json({ error: `Unexpected error. ${e}` });
}
};
export default handler;
And then call this endpoint in the apollo client config like that:
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink, concat } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { relayStylePagination } from "@apollo/client/utilities";
const getUserToken = async () => {
const res = await fetch("http://localhost:3000/api/get-user-token");
const { accessToken } = await res.json();
return accessToken;
};
const asyncAuthLink = setContext(async (request) => {
const token = await getUserToken();
return { ...request, headers: { authorization: token ? `Bearer ${token}` : "" } };
});
const httpLink = new HttpLink({ uri: process.env.NEXT_PUBLIC_API_URL });
const client = new ApolloClient({
name: "web",
version: "1.0",
uri: process.env.NEXT_PUBLIC_API_URL,
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: relayStylePagination(),
},
},
},
}),
link: concat(asyncAuthLink, httpLink),
});
export default client;
Upvotes: 0