express jwt error handling in nest js graphql - express

I use express JWT with nest js and use it in gateway graphql.
I want to return error when my token has error including expiration error or invalid error after calling any graphql api.
Here is the code that I use express JWT in main file of my gateway:
app.use(
graphqlUploadExpress(),
expressJwt({
secret: secretCallback,
algorithms: ['HS256'],
credentialsRequired: false,
}),
);
and the following code is my validation when token is invalid:
function (err, req, res, next) {
const { ip, method, originalUrl, headers } = req;
const requestMeta = { headers, ip, method, originalUrl, error: err };
if (err.message === 'jwt expired') {
Logger.error('Gateway JWT Expired', requestMeta);
res.send(401, { code: 'gateway-token-expired', message: 'Token is expired' });
} else if (err.code === 'invalid_token') {
Logger.error('Gateway JWT Invalid', requestMeta);
res.send(401, { code: 'gateway-token-invalid', message: 'Token is invalid' });
} else {
next(err);
}
};
I want that res.status(err.status).send({ message: err.message }); return the error message back to user.

Maybe this could help...
function (err, req, res, next) {
const { ip, method, originalUrl, headers} = req;
const requestMeta = { headers, ip, method, originalUrl, error: err};
if (err.message === 'jwt expired') {
Logger.error('Gateway JWT Expired', requestMeta);
res.status(err.status || 401).send({ message: 'Token is expired or gateway-token-expired'});
} else if (err.code === 'invalid_token') {
Logger.error('Gateway JWT Invalid', requestMeta);
res.status(err.status || 401).send({ message: 'Token is invalid or gateway-token-invalid'});
} else {
res.status(err.status || 501).send({ message: `${err.message} or ${err.code}`});
}
}

the problem was that when I send response to client I did not set headers to response to due to this client was getting CROS policy.
res
.status(401)
.set({ 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' })
.send({ code: 'gateway-token-expired', message: 'Token is expired' });

Related

Spotify returning 200 on token endpoint, but response data is encoded

I'm working through this tutorial on creating an app that uses the Spotify API. Everything was going great until I got to the callback portion of authenticating using the authentication code flow.
(I do have my callback URL registered in my Spotify app.)
As far as I can tell, my code matches the callback route that this tutorial and others use. Significantly, the http library is axios. Here's the callback method:
app.get("/callback", (req, res) => {
const code = req.query.code || null;
const usp = new URLSearchParams({
code: code,
redirect_uri: REDIRECT_URI,
grant_type: "authorization_code",
});
axios({
method: "post",
url: "https://accounts.spotify.com/api/token",
data: usp,
headers: {
"content-type": "application/x-www-form-urlencoded",
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
},
})
.then(response => {
console.log(response.status); // logs 200
console.log(response.data); // logs encoded strings
if (response.status === 200) {
res.send(JSON.stringify(response.data))
} else {
res.send(response);
}
})
.catch((error) => {
res.send(error);
});
Though the response code is 200, here's a sample of what is getting returned in response.data: "\u001f�\b\u0000\u0000\u0000\u0000\u0000\u0000\u0003E�˒�0\u0000Ee�uS\u0015��\u000e�(\b\u0012h\u0005tC%\u0010\u0014T\u001e�����0��^޳:���p\u0014Ѻ\u000e��Is�7�:��\u0015l��ᑰ�g�����\u0"
It looks like it's encoded, but I don't know how (I tried base-64 unencoding) or why it isn't just coming back as regular JSON. This isn't just preventing me logging it to the console - I also can't access the fields I expect there to be in the response body, like access_token. Is there some argument I can pass to axios to say 'this should be json?'
Interestingly, if I use the npm 'request' package instead of axios, and pass the 'json: true' argument to it, I'm getting a valid token that I can print out and view as a regular old string. Below is code that works. But I'd really like to understand why my axios method doesn't.
app.get('/callback', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
const code = req.query.code || null;
const state = req.query.state || null;
const storedState = req.cookies ? req.cookies[stateKey] : null;
res.clearCookie(stateKey);
const authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code',
},
headers: {
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
},
json: true,
};
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
const access_token = body.access_token;
const refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { Authorization: 'Bearer ' + access_token },
json: true,
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect('/#' + querystring.stringify({
access_token: access_token,
refresh_token: refresh_token,
}));
} else {
res.redirect(`/#${querystring.stringify({ error: 'invalid_token' })}`);
}
});
});
You need to add Accept-Encoding with application/json in axios.post header.
The default of it is gzip
headers: {
"content-type": "application/x-www-form-urlencoded",
'Accept-Encoding': 'application/json'
Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
}

Cant catch axios error in promise, response works fine

I am trying to catch an error whilst the user tries to access a page without an authentication token.
axios.js?v=012beb2f:840 POST http://localhost:3001/api/get-user 422 (Unprocessable Entity)
Uncaught (in promise) AxiosError {message: 'Request failed with status code 422', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
router.beforeEach((to, from, next) => {
const store = useUserStore()
if(to.meta.requiresAuth)
{
try
{
const response = axios.post('/api/get-user', {}, {
headers: {
Authorization: `Bearer ${store.user.token}`
}
})
.then(response => {
console.log(response)
next()
})
}
catch(error)
{
console.log(error)
next('/login')
}
}
else
{
next()
}
})
Thats the code that makes the request to the server. If the token is correct it works fine. However incorrect token throws the error mentioned above. I would like it to redirect to /login page if token is incorrect.
This is the code on server side.
router.post('/get-user', signupValidation, (req, res, next) => {
if(
!req.headers.authorization ||
!req.headers.authorization.startsWith('Bearer') ||
!req.headers.authorization.split(' ')[1]
){
return res.status(422).json({
message: "Please provide the token",
});
}
const theToken = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(theToken, 'the-super-strong-secrect');
db.query('SELECT * FROM users where id=?', decoded.id, function (error, results, fields) {
if (error) throw error;
return res.send({ error: false, data: results[0], message: 'Fetch Successfully.' });
});
});
Change the synchronous try/catch...
try
{
somePromise.then(...)
}
catch(error)
{
console.log(error)
next('/login')
}
...to instead use the catch() provided by the promise:
const headers = { Authorization: `Bearer ${store.user.token}` };
axios.post('/api/get-user', {}, { headers })
.then(response => {
console.log(response)
next()
})
.catch(error => {
console.log(error)
next('/login')
}}
Note, also, that the OP code incorrectly assigned the axios.post promise to an unused variable called "response".
Alternatively, use the synchronous try/catch style with async/await:
router.beforeEach(async (to, from, next) => {
const store = useUserStore()
if(to.meta.requiresAuth)
{
try
{
const headers = { Authorization: `Bearer ${store.user.token}` };
const response = await axios.post('/api/get-user', {}, { headers });
console.log(response);
next();
}
catch(error)
{
console.log(error)
next('/login')
}
}
else
{
next()
}
})

Vue resource interceptor to refresh JWT token

I am trying to write a vue-resource interceptor that refreshes the JWT access token, when it has expired. I assume that I need to have a post-request callback that checks for token expiration error (status == 401 in my case), and then calls the refresh endpoint and re-tries the initial request.
With the code below, the only problem I face is that the next() call after refreshing the token never happens, even though the refresh executes fine and new tokens are saved to the localStorage.
Vue.http.interceptors.push(function (request, next) {
if (!request.headers.has('Authorization') && window.localStorage.access_token) {
request.headers.set('Authorization', `Bearer ${window.localStorage.access_token}`);
}
next(function (response) {
if (response.status === 401) {
const refreshToken = localStorage.getItem('refresh_token');
Vue.http.post('/api/v4/auth/refresh', null, {
headers: {'Authorization': `Bearer ${refreshToken}`}
}).then((response) => {
localStorage.setItem('access_token', response.data.accessToken);
localStorage.setItem('refresh_token', response.data.refreshToken);
}).then(() => {
next();
});
}
});
})
I think the chaining of callbacks is not quite right. The second call to next should happen when the first call returns a promise, and should not depend on the logic applied to resolve the first promise, only on successful resolution of that promise.
Vue.http.interceptors.push(function (request, next) {
if (!request.headers.has('Authorization') && window.localStorage.access_token) {
request.headers.set('Authorization', `Bearer ${window.localStorage.access_token}`);
}
next(function (response) {
if (response.status === 401) {
const refreshToken = localStorage.getItem('refresh_token');
return Vue.http.post('/api/v4/auth/refresh', null, {
headers: {'Authorization': `Bearer ${refreshToken}`}
}).then((response) => {
localStorage.setItem('access_token', response.data.accessToken);
localStorage.setItem('refresh_token', response.data.refreshToken);
})
}
return Promise.resolve(response)
}).then(() => {
next(); // This is called whenever the first callback returns a promise
});
})

How to set token into onError ApolloClient?

I want to handle error if return error will call setContext for query again but I can't get token in local state because onError does not accept async. How I can do that.
Thanks
//
export default async () =>
new ApolloClient({
cache: new InMemoryCache({
dataIdFromObject: object => object.key || null
}),
uri: Api.GRAPH_QL_URL,
clientState: { defaults, resolvers },
// cho handle network failed
///onError not accept async
onError: ({ graphQLErrors, networkError, operation, forward }) => {
// how to get token in hear
if (networkError) {
operation.setContext({
headers: {
Accept: "application/json",
authorization: token !== null ? `JWT ${token}` : "" // how to put token into authorization
}
});
}
},
// Call query
request: async operation => {
const token = await AsyncStorage.getItem(strings.keyToken);
console.log("Client request: ", {
operationName: operation.operationName,
variables: operation.variables,
query: operation.query,
jwtoken: token
});
operation.setContext({
headers: {
Accept: "application/json",
authorization: token !== null ? `JWT ${token}` : ""
}
});
},
});
//I want to handle error if return error will call setContext for query again but I can't get token in local state. How I can do that.
Thanks
You need to set an Observable in order to work with async/await inside the onError method.
Import it from apollo-link (import { Observable } from 'apollo-link';).
onError: ({ graphQLErrors, networkError, operation, forward }) => {
if (networkError) {
return new Observable(async (observer) => {
const token = await AsyncStorage.getItem(strings.keyToken);
const headers = operation.getContext().headers;
operation.setContext({
headers: {
...headers,
Authorization: token !== null ? `JWT ${token}` : ''
}
});
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
return forward(operation).subscribe(subscriber);
});
}
}

Redirect_uri mismatch in fetch and gapi

working on connecting users to google, and we're trying to get their access and refresh tokens from the google api, and we're getting an issue exchanging the OAuth2 Code for tokens. Both sets of code have the same error.
I initialize the gapi client and fill in the information needed like so:
gapi.load('client:auth2', _ => {
gapi.client.init({
'apiKey': 'omitted for security',
clientId: 'omitted for security',
'scope': 'https://www.googleapis.com/auth/drive',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
}).then(_ => {
gapi.auth2.getAuthInstance().grantOfflineAccess().then(resp => {
if(resp.code){
gapi.client.request({
path: 'https://www.googleapis.com/oauth2/v4/token',
method: 'post',
params: {code: resp.code},
body: {
code: resp.code,
client_id: opts.clientId,
client_secret: 'omitted for security',
grant_type: 'authorization_code',
redirect_uri: 'omitted for security',
access_type: 'offline'
},
}).then((onfulfill, onreject, context) => {
console.log('fulfilled', onfulfill);
console.log('rejected: ', onreject);
console.log('context', context);
}).catch(err => console.error(err.body));
}
});
});
});
What I'm trying to do in the .then() is to call the token endpoint to exchange the code in the response for a refresh and access token to store in my back end and the user's local storage.
I get this error response from both versions of the code. (better, more reliable code is provided here.)
{ "error": "redirect_uri_mismatch", "error_description": "Bad
Request" }
I also have a backend setup stashed as a last resort that accepts the code from gapi.auth2.getAuthInstance().grantOfflineAccess() calls the token endpoint, and returns the access_token and refresh_token to the client.
This code is similar, but not quite. instead of using the google api library, I used fetch, and it works fine. (Fetch and XHR on the front end have the same issues as the gapi.client.request function....)
const gConfig = require('./basic.json');
const scopes = ['https://www.googleapis.com/auth/drive'];
const { client_id, client_secret, redirect_uris } = gConfig.web;
const authClient = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
app.post('/', (req, res) => {
const { code } = req.body;
console.log('Received Code From Request: ', code);
let data = { code , client_id, client_secret,redirect_uri: redirect_uris[0], grant_type: 'refresh_token'};
let encodedParams = Object.keys(data).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(data[k])).join('&');
fetch(
`https://www.googleapis.com/oauth2/v4/token?code=${code}`,
{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: encodedParams }
).then((res) => {
console.log('called the api with fetch');
console.dir(res.json());
});
authClient.getToken(code, (err, token) => {
if (err) {
console.error(err);
res.status(500).json(err);
}
// console.dir(token);
console.log('TOKEN: =>', token);
res.json(token);
});
});
Is there anyone that's done this on the front end successfully?
You can't get a refresh token in a browser. Your example code would only work on a server. To do oauth at the client you should request "token" instead of "code".