Apollo Client Delaying the Authorization Header - react-native

I am using Apollo (with Graph Cool), redux, and Auth0 in a React-Native app. I am trying to delay the queries and mutations until the header is set.
The idToken is stored in Async Storage, and is therefore a promise. I can't use redux to pass the token, because that would create a circular dependency.
When the user logins in for the first time or the token has expired, the queries are sent before header is set, which means I get the error Error: GraphQL error: Insufficient Permissions
How can I delay the queries until the token is found and added to the header? I have been searching three main solutions:
Add forceFetch: true; This seems to be part of an earlier implementation of the Apollo client. Even if I find the equivalent, the app still fails on the first attempt to fetch.
Reset the store (rehydrate?) upon logging in. This is still asynchronous so I don't see how this could affect the outcome.
Remove all mutations and queries from login itself, but due to the progress of the app, this is not feasible.
Some snippets:
const token = AsyncStorage.getItem('token');
const networkInterface = createNetworkInterface({ uri:XXXX})
//adds the token in the header
networkInterface.use([{
applyMiddleware(req, next) {
if(!req.options.headers) {
req.options.headers = {}
}
if(token) {
token
.then(myToken => {
req.options.headers.authorization = `Bearer ${myToken}`;
})
.catch(err => console.log(err));
}
next(); // middleware so needs to allow the endpoint functions to run;
},
}]);
// create the apollo client;
const client = new ApolloClient({
networkInterface,
dataIdFromObject: o => o.id
});
and
const store = createStore(
combineReducers({
token: tokenReducer,
profile: profileReducer,
path: pathReducer,
apollo: client.reducer(),
}),
{}, // initial state
compose(
applyMiddleware(thunk, client.middleware(), logger),
)
);

I'm not certain this will work without a reproduction app, mostly because I don't have an app of your structure set up, but you're hitting this race condition because you are calling next() outside of your async chain.
Calling next() where it is currently will tell the client to continue on with the request, even if your token isn't set. Instead, let's wait until the token comes back and the header gets set before continuing on.
networkInterface.use([{
applyMiddleware(req, next) {
if(!req.options.headers) {
req.options.headers = {}
}
AsyncStorage.getItem('token')
.then(myToken => {
req.options.headers.authorization = `Bearer ${myToken}`;
})
.then(next) // call next() after authorization header is set.
.catch(err => console.log(err));
}
}]);

Related

OctoKit with Auth0 (Github Login) in NextJS

I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?
I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)

How do I pull data out of response or cache for apollo client 3

I am trying to pull a jwt out of a loginUser mutation and store it in a variable then use the apollo-link to setContext of the header via "Authorization: Bearer ${token} for authentification as all my other mutations and queries require the token. I have been slamming the docs for days on Apollo Client(React) -v 3.3.20. I have been through all the docs and they show all these examples of client.readQuery & writeQuery which frankly seem to just refetch data? I don't understand how you actually pull the data out of the response and store it in a variable.
The response is being stored in the cache and I have no idea how to take that data and store it in a token variable as I stated above. Which remote queries I can just access the returned data via the data object from the useQuery hook, however on the useMutation hook data returns undefined. The only thing I could find on this on stack overflow was the my data type may be custom or non-traditional type but not sure if that is the problem.
[Cache in apollo dev tools][1]
[Mutation in apollo dev tools][2]
[Response in network tab][3]
Here is my ApolloClient config:
const httpLink = createHttpLink({ uri: 'http://localhost:4000/',
// credentials: 'same-origin'
});
const authMiddleware = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('token');
// add the authorization to the headers
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}));
return forward(operation);
})
const client = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
});
The header works obviously I just can't grab the token to pass so the header just sends Authorization: Bearer.
For the login I have this:
const LOGIN_USER = gql`
mutation($data:LoginUserInput!) {
loginUser(
data: $data
) {
user {
id
name
}
token
}
}
`;
const [loginUser, { data, loading, error }] = useMutation(LOGIN_USER);
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
Originally I was just calling
onClick={loginUser( { variables })}
For the login but onComplete never works and everywhere I look I see lots of posts about it with no solutions. So I tried slamming everything into a function that I then called with loginUser inside it:
const submit = async () => {
loginUser({ variables})
// const { user } = await client.readQuery({
// query: ACCESS_TOKEN,
// })
// console.log(`User : ${JSON.stringify(user)}`)
const token = 'token';
const userId = 'userId';
// console.log(user);
// localStorage.setItem(token, 'helpme');
// console.log({data});
}
At this point I was just spending hours upon hours just trying mindless stuff to potentially get some clue on where to go.
But seriously, what does that { data } in useMutation even do if it's undefined. Works perfectly fine for me to call data.foo from useQuery but useMutation it is undefined.
Any help is greatly appreciated.
[1]: https://i.stack.imgur.com/bGcYj.png
[2]: https://i.stack.imgur.com/DlzJ1.png
[3]: https://i.stack.imgur.com/D0hb3.png

How to implement silent token refresh with axios request interceptor?

We are implementing a token-based authentication and when a user signs in we generate access and refresh tokens then save that with the timestamp on device so we can later check if the access token is expired or not.
We are currently using axios interceptor before every request and checking if the token is still valid or not with the timestamp we saved earlier when we generated the access and refresh tokens, but when the access token is expired and we are making a request to refresh the token the app goes on an infinite loop and none of the requests go through (both the original and refresh token api requests). you can see my code below:
const instance = axios.create({
baseURL: 'http://localhost:8080'
});
const refreshToken = () => {
return new Promise((resolve, reject) => {
instance
.post('/token/renew')
.then(response => {
resolve('refresh successful');
})
.catch(error => {
reject(Error(`refresh fail: ${error}`));
});
});
};
instance.interceptors.request.use(
async config => {
const timestamp = 1602155221309;
const diffMinutes = Math.floor(Math.abs(Date.now() - timestamp) / 60000);
// if diffMinutes is greater than 30 minutes
if (diffMinutes > 30) {
const tokenResponse = await refreshToken();
return config;
}
return config;
},
error => {
return Promise.reject(error);
}
);
The infinite loop is caused by your interceptor triggering another Axios request on which said interceptor will also run and trigger another Axios request, forever.
A simple solution would be to make the refresh token network request using the default axios instance which doesn't include any interceptors:
const refreshToken = () => {
// You can skip returning a new `Promise` since `axios.post` already returns one.
return axios.post("YOUR_BASE_URL/token/renew");
};
Obviously that also means you'll have to write a bit of logic to send the current refresh token along if that's included in your instance interceptors.

How should I handle nuxt cookies expiration and workflow?

I have made an authentication workflow for a project using a Nuxt frontend(universal mode) and an Apollo endpoint as backend.
It is a mix of several examples I found and, with SSR, and since I do not fully anticipate what could go wrong, I wanted to make sure there is no red flag about how I proceed.
On the backend, I use an express middleware to sign JWT auth tokens, check them, and return them in the Authorization header. Here is the middleware:
import jwt from 'jsonwebtoken';
import { AuthenticationError } from 'apollo-server-express';
export const getToken = payload => {
return jwt.sign(payload, process.env.SEED, { expiresIn: process.env.EXPTOKEN });
}
export const checkToken = (req, res, next) => {
const rawToken = req.headers["authorization"]
if (rawToken) {
try {
const token = rawToken.substring(7)
// Verify that the token is validated
const { user, role } = jwt.verify(token, process.env.SEED);
const newToken = getToken({ user, role });
req.user = user;
req.role = role;
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.set("Access-Control-Expose-Headers", "authorization");
res.set("authorization", newToken);
} catch (error) {
if (error.name === "TokenExpiredError") {
res.set("Access-Control-Expose-Headers", "authorization");
res.set("authorization", false);
}
console.log("invalid token", error);
return new AuthenticationError
// Invalid Token
}
}
next();
}
Since there is a Nuxt-Apollo module, I used its methods onLogin, onLogout and getToken to store the JWT string in a cookie. As I understand it, SSR apps don't have the serverside local storage matching the client so they have to use cookies. Correct?
Here is my nuxt middleware where I check the users credentials before allowing them to visit an auth route. Is is quite messy but it gets the job done, except for the commented part.
export default function ({ app, route, error, redirect }) {
const hasToken = !!app.$apolloHelpers.getToken()
// this part does not work
/* const tokenExpireDateTime = app.$cookies.nodeCookie.parse('cookie-name', 'expires')
if (hasToken && tokenExpireDateTime < 0) {
error({ statusCode: 403, message: 'Permission denied', description: 'Sorry, you are forbidden from accessing this page.' })
app.$apolloHelpers.onLogout()
return redirect('/login')
}
*/
if (!hasToken) {
if (route.name === 'welcome-key') {
// enrollment link route
} else {
if (route.name === 'home') {
error({ errorCode: 403, message: 'You are not allowed to see this' })
return redirect('/showcase')
}
if (!['login', 'forgot_password', 'reset_password-key'].includes(route.name)) {
error({ errorCode: 403, message: 'You are not allowed to see this' })
return redirect('/login')
}
}
} else {
if (['login', 'forgot_password', 'reset_password-key'].includes(route.name)) {
redirect('/')
}
}
}
I have one issue and several points of confusion.
My issue is that I can't get the cookie expires value to redirect in the above nuxt middlware if it is necessary to login again because the JWT is expired. I used the piece of code mentioned in this issue as reference.
With this issue, my confusion is about:
The expires date on the cookie is set by the Nuxt-Apollo module, I expect, and I have to make it match the duration set on server (i.e. process.env.EXPTOKEN in the server middleware mentioned above), correct?
That expiration time alone can easily be tempered with and the real security is the lack of a valid token in headers when a request is handled by my server middleware. Its use is for client-side detection and redirect of an expired token/cookie, and serverside prefetch of user related data during SSR. Right?
The new token emitted by my express backend middleware is not taken into account in my frontend: it is not updating the cookie stored JWT and expires value client side. I mean that I can see the autorization header JWT string being updated in the response, but the cookie isn't. The following request still use the first JWT string. Am I supposed to update it at each roundtrip? What am I missing with the approach of the express middleware (that, as you can guess, I didn't write)
Please help me understand better this workflow and how I could improve it. It tried to avoid as much as possible to make this question too broad, but if I can narrow it down more, feel free to suggest an edit.

VueJS / Vuex App - Validate JWT Token USED for Authentication

I have a VueJS App, using Vuex & Vue Router.
I have 3 components (which are also pages): Home, Login and a Protected Page which requires one to be authenticated.
The login page make a POST call to the backend API which returns a token if the credentials are valid.
methods: {
sendCredentials: function() {
const { email, password } = this
this.$store.dispatch(AUTH_REQUEST, {email, password})
.then(() => {
this.$router.push('/')
})
.catch((err) => console.log(err.response));
}
}
Here is the related action:
actions: {
[AUTH_REQUEST]: ({ commit, dispatch }, user) => {
return new Promise((resolve, reject) => {
commit(AUTH_REQUEST);
axios.post('http://localhost:3000/api/login', user)
.then((resp) => {
const token = resp.data.token;
localStorage.setItem('userToken', token);
commit(AUTH_LOGIN, token);
resolve(resp);
})
.catch(err => {
commit(AUTH_ERROR, err);
localStorage.removeItem('userToken');
reject(err);
})
});
}
I have used navigation guard to block access to the protected page if the user is not logged in.
This is actually working: When I go the protected page, I'm asked to log in. When I use the rights credentials, I'm able to access the protected page.
I have yet a huge bug: When I put any random string on the localStorage as the userToken, I can access the protected page...
How to prevent that ?
The initial state is defined as below:
state: {
token: localStorage.getItem('userToken') || '',
},
Is there a way to validate the userToken which I get through the localStorage when I set up the initial state of token ?
I have been wondering the same thing a while ago. What I ended up with is to check the token against your backend on initial loading of your page. If the token is valid you commit it to Vuex, if the token is invalid, you delete everyting from localStorage.
This leads to the outcome where someone hypothetically could replace the token after initial load with their own invalid token, but if the clientside token is already validated, what would be the point? If you want to secure against this scenario as well you could apply the same logic in your navigation guard. So not just check for a token, but validate the token against your backend on each route change and clear localStorage if invalid. I think this will come at a performance disadvantage though due to the extra API call.