Would like to check, I am using AngularJS 1.4.4 with AngularUI. So recently I have upgraded my site to use JWT tokens. The JWT tokens are being sent via headers in http request. So I've also added an interceptor to intercept all requests Angular made to the server like this
request: function(config){
config.headers = config.headers || {};
if ($localStorage.jwtToken) {
config.headers.Authorization = 'Bearer ' + $localStorage.jwtToken;
}
return config;
},
response: function(response) {
if(response.headers('New-Jwt-Token')){
console.log(response.headers('New-Jwt-Token'));
}
},
responseError: function (response) {
// handle the case where the user is not authenticated
if (response.status === 401) {
if(response.data.error === 'token_ttl_expired'){
if(response.data.new_jwt_token){
$localStorage['jwtToken'] = response.data.new_jwt_token;
console.log('jwt updated');
}
}
}
return $q.reject(response);
}
What I have noticed is while angular made the requests there is also many anonymous template requests being made to routes that dont exist in my website which causes then entire program to stop working.
How should i remove / prevent / delete these unnecessary templates from loading?
Update: If found out this is actually cause by the AngularUI bootstrap.
How should i prevent this from being intercepted by the interceptor?
Your error is more than likely caused by no response being returning in your response interceptor. So it should be:
response: function(response) {
if(response.headers('New-Jwt-Token')){
console.log(response.headers('New-Jwt-Token'));
}
return response;
},
The request interceptor will intercept all requests AFAIK. Note that with ui-bootstrap, the templates are cached in the $templateCache so no actual GET is actually sent to your server.
This is completely unnecessary but what you can do in your request interceptor is to do nothing if found in the $templateCache.
request: function(config){
if (config.url && $templateCache.get(config.url)) {
// Don't do anything
return config;
}
config.headers = config.headers || {};
if ($localStorage.jwtToken) {
config.headers.Authorization = 'Bearer ' + $localStorage.jwtToken;
}
return config;
},
Again, this is not necessary and doesn't achieve much but I put it here incase you have other processing logic in the request interceptor.
Related
I’m using the Got library for HTTP requests within my application. I’m making HTTP requests to an API where I can expect an HTTP code 404 under certain conditions. I’d like to use Got's internal retry functionality for rerunning the request until the 404 error is gone (which will happen; I just don't know if it takes 1 minute or 30 minutes).
From the documentation I know that HTTP code 404 is not a supported statusCode for the built-in retry functionality and therefore I cannot perform any action within the beforeRetry hook of Got; see here.
I’m extending a Got instance to allow some presets for the API I’m calling. For now I have not found a clean way to extend the existing retry statusCodes with 404.
const gitlabOnPrem = got.extend({
prefixUrl: ".." + "..",
mutableDefaults: true,
responseType: 'json',
//retry: { statusCode: got.defaults.options.retry.statusCode.push(404) }, //does not work | not clean
https: { rejectUnauthorized: false },
headers: {
'PRIVATE-TOKEN': "..",
Accept: 'application/json',
},
hooks: {
beforeError: [
(error) => {
const { response } = error;
console.log(response);
/*
Another idea: if I cannot extend retry statusCodes then I´d like to somehow force a retry from here
if (response.statusCode === 404 && response.path.endsWith('/export/download')) {
console.log('FORCE A RETRY AS THE DOWNLOAD MIGHT NOT BE READY YET');
}
*/
if (response && response.body) {
error.name = 'GitLabOnPremError';
error.message = `${response.body.message !== undefined
? response.body.message
: response.body.error
} (${response.statusCode})`;
}
return error;
},
],
},
});
How can I extend the HTTP statusCodes that allow running a retry?
If this is not possible, see my comment in the code. Is it somehow possible to force a retry manually by just using Got?
Sometimes it's best to just write it yourself. Trying to get a library to work in a way that it isn't totally made to do can be more pain than it's worth. It's also usually very brittle in the long run.
Why not just wrap it yourself? Something like this:
async function MyGotFn() {
let retries = 100;
let statusCode = 404;
let response;
while (statusCode === 404 && --retries > 0) {
response = await got('...');
statusCode = response.statusCode;
}
if (response.statusCode === 404) throw new Error('max retries reached');
return response;
}
I'm trying to write a response interceptor for my React project but I am having some issues.
When a user gets a 401 from their original request I want to try and refresh the token and continue, but if the user gets a 401 from their original request and when trying to refresh the token it fails then redirect them to the login page.
What I have does the first bit just fine, it refreshes the token and continues with the original request, but the issue i am having is that if the refresh fails, its not redirecting the user to the login page.
I would love some input on what I am doing wrong
import axios from 'axios';
import { useRouter } from 'next/router'
const router = useRouter();
const apiInstance = axios.create({
baseURL: process.env.API_URL
});
apiInstance.interceptors.response.use((response) => {
return response;
}, async function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && originalRequest.url === '/oauth/token') {
router.push('/');
return Promise.reject(error);
}
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
return axios.post(process.env.OAUTH_BASE_URL + '/oauth/token', {
'grant_type': 'refresh_token',
'refresh_token': localStorage.getItem('refresh_token'),
'client_id': process.env.CLIENT_ID,
})
.then(res => {
if (res.status === 200) {
localStorage.setItem('access_token', res.access_token);
localStorage.setItem('refresh_token', res.refresh_token);
localStorage.setItem('expires_in', res.expires_in);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('access_token');
return apiInstance(originalRequest);
}
})
}
return Promise.reject(error);
});
export default apiInstance;
There's a couple of errors here. First, url property is equal to the whole value of url param of axios call, so this...
originalRequest.url === '/oauth/token'
... is only true if process.env.OAUTH_BASE_URL is an empty string (and most likely it's not). In general, it's better to avoid checking against URLs and use flags/custom properties set on request objects (as with _retry flag).
Also, note that while apiInstance is used for regular API call, the particular call for refresh token actually avoids it:
return axios.post(process.env.OAUTH_BASE_URL + '/oauth/token', { //
^^^^^^^^^^
... which means interceptors for this call are not even fired.
Here's one possible approach to solve this. apiInstance here is the exported axios instance, and setTokens/getAccessToken/getRefreshToken are simple abstractions over mechanisms of storing/retrieving particular tokens.
apiInstance.interceptors.request.use(request => {
if (!request._refreshToken) {
request.headers.Authorization = 'Bearer ' + getAccessToken();
}
// console.log('REQUEST', request.method + ' ' + request.url);
return request;
});
apiInstance.interceptors.response.use(
void 0, // better skip this argument altogether
error => {
const originalRequest = error.config;
if (originalRequest._refreshToken) {
console.log('REFRESH TOKEN FAILED');
// ... and all the things you need to do when refreshing token failed,
// like resettting access token, and rerouting users to /login page,
// or just sending an event for Router to process
return Promise.reject(error);
}
const errorResponse = error.response;
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
return apiInstance.post('/oauth/token', {
grant_type: 'refresh_token',
refresh_token: getRefreshToken(),
client_id: process.env.CLIENT_ID,
}, {
_refreshToken: true // custom parameter
}).then((resp) => {
setTokens(resp.data);
return apiInstance(originalRequest);
});
}
);
There are two ideas behind this (easily testable with unit tests): first, failed refresh token requests always stop the interceptor chain (as they throw immediately), second, if 'business-level' API request fails, it's always preceded with refresh-token one.
Note that this code is just a prototype to illustrate the concept here. If you expect your code to be able to issue multiple API calls at once, token refresh should actually be wrapped into a function returning single promise (to avoid subsequent refresh-token calls). If you're going to use this in production, I strongly suggest at least considering using axios-auth-refresh instead of writing your own implementation for that.
Is there a way to not just intercept but also respond to an axios request before it has been sent off? As in, send a request from the browser and respond to it from the browser + prevent it from sending request.
I know that I can use axios interceptors to intercept the request and response before it is sent and returned to the component and I know that in the request interceptor I can throw an error and trigger the response interceptor with a failed request. How can I do the same for a successful request? Given certain conditions I want axios to respond as if it passed to the server when it actually never made it past the interceptor. is this possible?
Here's pseudo code for what I've got so far:
axios.interceptors.request.use(
request => {
if (localResponse) {
throw { isLocal: true, data: { hello: 'world' } }; // <- this will stop request and trigger
// response error. I want to trigger
// the actual response callback
} else {
return request; // <- will perform full request
}
},
error => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
return response; // <- I want to trigger this callback
},
error => { // <- so far i can only trigger a response error
if (error?.isLocal) {
return Promise.resolve(error.data);
}
return Promise.reject(error);
}
);
I've tried just resolving the request interceptor but that tries to continue to fulfill the request. Does anyone have any creative ideas for solving this problem? Maybe there's a better solution than using interceptors?
Managed to compose a solution myself. This way all my axios calls will not have to be altered, i can just change behavior of interceptor.
NOTE: If anyone comes up with a better solution I will happily tag their answer as correct and upvote. For now this is the best solution I can come up with.
SOLUTION
Here's how i was able to resolve the problem
axios.interceptors.request.use(
request => {
if (localResponse) {
throw { isLocal: true, data: { hello: 'world' } }; // <- this will stop request and trigger
// response error. I want to trigger
// the actual response callback
} else {
return request; // <- will perform full request
}
},
error => {
return error?.isLocal
? Promise.resolve(error); // <- triggers response intercept
: Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
return response;
},
error => {
error?.isLocal
? Promise.resolve(error); // <- sends as successful response
: Promise.reject(error);
}
);
Essentially what I'm doing is throwing an error to prevent the request from going through, but then resolving the error instead of rejecting it. It's a little hacky but it gets the job done.
Can you just skip the request altogether in the local scenario?
function getData() {
if (localResponse) {
return Promise.resolve({ isLocal: true, data: { hello: 'world' }});
}
else {
return axios.whatever;
}
}
...
getData().then(...)
Situation
From my Meteor.js website I'm calling my own REST service. Here's a code sample from my server side
function (question) {
var r = Async.runSync(function (done) {
HTTP.get(URL, {
params: {q: question}, headers: {
"Accept": "application/json",
}
}, function (err, result) {
done(err, result);
});
});
if (r.err) {
console.log("Failed to smartSearch ... ", r.err);
return null;
} else if (r.result.content) {
console.log("Success ... ");
return JSON.parse(r.result.content);
}
}
This works great but there is also some crucial information in the response headers which I'm unable to find.
What I've tried so far
I viewed everything within r.result.content, but this only contains my request headers.
I've installed https://atmospherejs.com/gadicohen/headers and tried everything the site said.
But still not seeing my response headers.
Additional Information
I'm fairly new to Meteor.js so I don't really have an idea what I might be doing wrong but getting response headers doesn't see like a strange thing to me.
There is no need to wrap the request as an async call, as it already is.
You can use a try..catch block to handle both successful and failed requests.
try {
var result = HTTP.get(...);
var responseHeaders = result.headers;
} catch (e) {
// handle error
}
If the response headers indicate JSON response, it will be parsed and available as result.data. The response will be available as a string in result.content.
More details are available in the HTTP package API docs.
I have a simple Interceptor in angular that intercepts requests and adds a authorization header. It also intercepts a response error of 401 to know if the request failed because of authorization.
Unfortunately it seems to mess with $resource, because my $resource calls ALWAYS return the success callback and never an error (be it 400 or 500 whatever).
It's definitly the interceptor, because if I remove it, the $resource calls return with the correct callback.
Any ideas on how to fix this behavior?
Here's the interceptors request:
function request(config) {
var token = 'whatever-my-token-is';
if (token) {
config.headers.authorization = token;
}
return config;
}
And the responseError:
function responseError(response) {
if (response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
return response;
}
Any help appreciated
I think you need to use a promise to return the error.
adding $q to your Interceptor factory.
like so
$provide.factory('MyHttpInterceptor', function ($q){
...
})
and then have the responseError()
function responseError(response) {
if (response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
return $q.reject(response);
}
this link might help also https://djds4rce.wordpress.com/2013/08/13/understanding-angular-http-interceptors/