I know that http 302 responses are handled directly by the browser, and because of that you cannot acces any of the request properties from your source code. But I am wondering if there is any way of intercepting the 302 redirect response. Let me explain myself:
My Frontend (Angular) makes an http request to A (I intercept the outgoing request)
A responds with 302 Location: B
My Frontend intercepts the 302 response with empty fields, and goes to B
Here I'd like to intercept the response coming from B
This is my Angular http interceptor code:
#Injectable()
export class CasInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('->Interceptor');
console.log(req);
return next.handle(req).map((event: HttpEvent<any>) => {
const response = event as HttpResponseBase;
console.log('<-Interceptor');
console.log(response);
return event;
});
}
}
You should get full header from http response.
{observe:"response"} is the magic parameter of angular http client.
So try this one
this.http
.get<any>(requestURL,{observe:"response"})
.subscribe(
data => {
console.log(data.header); //you will see full header here
console.log(data.url); // you can see redirect url from backend and handle it whatever you want
},
err => {
console.log(err)
}
Related
I'm trying to fetch the response from the below code. Which gives the JSON output. In server i'm getting 415 issue and the response also not coming.
constructor(private http: HttpClient){}
public getReports(postData: IRreq) {
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
return this.http.post(sampleurl, JSON.stringify(postData), { headers: headers })
.pipe(map((res:any) => this.report = res));
};
No issue from the API, getting 200 and response in postman. Not sure what was the issue here, need some help on this. Thanks
You are using example for Angular 2. That times json content-type was not standard way.
Now when you want to make request with json content-type you only do following:
const body = { title: "Angular PUT Request Example" };
this.http
.put<any>("https://jsonplaceholder.typicode.com/posts/1", body)
.subscribe((data) => console.warn(data));
So you don't need to do any magic with json encoding etc.
I know that I can use a service worker to intercept outgoing fetch operations, and even generate custom responses to them, e.g.
self.addEventListener('fetch', (event) => {
if (/\.jpg$/.test(event.request.url)) {
event.respondWith(
fetch('/images/anotherimage.jpg'));
}
});
However, what if I want to intercept the response to a given fetch request before that response is passed back to the page? Is this doable?
To be clear, I do not in any way want to modify the request itself - I just want to access the response to it.
I'm assuming that you mean you'd like to add logic to a service worker so that it requests a resource, and then modifies the response from the network, resulting in a response that's passed back to the page that is a mix of what you'd get from the network and what the service worker added.
If so, the answer is yes, you can do that for same-origin responses, and for cross-origin responses when CORS is used. (You can't modify opaque responses, which is what you get when making a cross-origin request without using CORS.)
Here's an example of a fetch handler that responds to requests for an hypothetical /api endpoint that returns JSON my making the request to /api, and then adding in an additional field to the API response before returning the response to the page.
async function modifyAPIResponse(request) {
const apiResponse = await fetch(request);
const json = await apiResponse.json();
json.extraField = 'set by fetch handler';
return new Response(JSON.stringify(json), {
// Ensure that the Content-Type: and other headers are set.
headers: apiResponse.headers,
});
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.pathname === '/api') {
event.respondWith(modifyAPIResponse(event.request));
}
});
I crafted an API with Symfony 4 that uses a custom token verification. I tested the API on Postman and everything works perfectly, now I want to use the API using jQuery and fetch all the data , but in the browser, I'm facing CORS issues like below:
Access to XMLHttpRequest at 'http://localhost:8000/api/reports' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Here is my server-side API:
I've implemented a CORSEventSubscriber to allow the CORS like below :
class CORSSubscriber implements EventSubscriberInterface
{
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onKernelResponse(FilterResponseEvent $event)
{
$responseHeaders = $event->getResponse()->headers;
$responseHeaders->set('Access-Control-Allow-Origin', '*');
$responseHeaders->set('Access-Control-Allow-Headers', 'x-auth-token, content-type');
$responseHeaders->set('Access-Control-Allow-Methods', 'POST, GET');
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
This is the action that I'm calling in the controller:
/**
* #Route("/api/reports",name="reports",methods={"GET","OPTIONS"})
* #param Request $request
* #return Response
* #throws Exception
*/
function getReports(Request $request){
return new JsonResponse('test', Response::HTTP_UNAUTHORIZED);
}
I tried consuming the API like this
<script>
$(document).ready(function(){
authenticate().then(function (data) {
// the promise resolve the token and data.token output the correct value
$.ajax({
url:'http://localhost:8000/api/reports',
type: 'GET',
headers: {'X-Auth-Token' : data.token },
success: function (data) {
//append data to your app
console.log(data);
}
})
})
});
function authenticate() {
let data={
"username": "test",
"password": "test"
};
return new Promise(function(resolve, reject) {
$.ajax({
url:'http://localhost:8000/api/auth/check',
type:'POST',
data:JSON.stringify(data),
dataType:'Json',
success: function (data) {
console.log(data);
resolve(data);
},
error:function () {
}
})
});
}
</script>
I added this to debug closely the issue and i found out that this function only executes for POST when there's a token method like OPTIONS it doesn't execute
public function onKernelRequest(GetResponseEvent $event)
{
$this->logger->info($event->getRequest()->getRealMethod());
}
You are making a cross-origin request and adding a non-standard header. This means it is a Preflighted Request.
The browser is sending an OPTIONS request to ask permission to make the request with custom headers.
You can't control the format of the preflight request. You definitely can't add credentials to it. (Adding credentials is another thing which turns a simple request into a preflighted request).
You need to response to the OPTIONS request with permission via CORS headers. Since the request won't have any credentials associated with it your server must not require credentials.
Change the server to remove the requirement for the credentials when the request type of OPTIONS.
I don't know my way around the server-side framework you are using, but extrapolating from the code you've provided I suspect you should provide separate routes for GET and OPTIONS.
The OPTIONS request should concern itself only with CORS (and not fetch any data which requires authorisation).
The GET request should require authorisation and return the data.
After days through this, I fixed it by adding to the CorsSubscriber
public function onKernelResponse(FilterResponseEvent $event)
{
$responseHeaders = $event->getResponse()->headers;
$responseHeaders->set('Access-Control-Allow-Origin', 'http://localhost:8080');
$responseHeaders->set('Access-Control-Allow-Credentials', 'true');
$responseHeaders->set('Access-Control-Allow-Headers', ' content-type ,x-auth-token');
$responseHeaders->set('Access-Control-Allow-Methods', 'POST, GET');
if($event->getRequest()->getRealMethod()=='OPTIONS'){
$responseHeaders->set('Access-Control-Max-Age', '1728000');
$event->getResponse()->setStatusCode(200);
}
}
after handling the response I send 200 as status code so I won't have any CORS issue
I have a js client (vuejs) and a backend using DRF both in local.
I use this package to generate the token : https://github.com/davesque/django-rest-framework-simplejwt
I use this package https://www.npmjs.com/package/axios-auth-refresh to handle refresh token logic.
The main goal is to intercept a request when it return a 401 response, perform a refresh token request and then resolve the orginal request with the new token.
It works when the original request is a GET request but not when it is a POST request.
When using a POST request :
The orgin request fall in 401 when the token expire then the interceptor occur but the server respond with 405 method not allowed:
-https://imgur.com/C1tchvb
the method from the request from the interceptor does not match the method in the code shown above (line 3 & 4) : as you can see the server receive the payload from the origin request as method of the request :
-https://imgur.com/nlAknMi
I found this post : App Script sends 405 response when trying to send a POST request
i try to change the headers as advised but it did not work
How is the payload from the orginal resquest becoming the method of the interceptor when the origin request is a Post request with a payload ?
Here the code from the javascript client :
const refreshAuthLogic = failedRequest => axios(
{
method: 'post',
url: 'auth/refresh',
data: { refresh: store.state.token.refresh }
}).then(tokenRefreshResponse => {
store.dispatch('refreshToken', tokenRefreshResponse.data)
return Promise.resolve()
})
const instance = axios.create({
baseURL: '/api/'
})
instance.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${store.state.token.access}`
return config
})
createAuthRefreshInterceptor(instance, refreshAuthLogic)
EDIT
I manage to get it work but i don't really understand:
the problem is related to DJANGO/ DRF and not axios
it seems that when a POST request is done and fail ( here with 401) the server keeped the data.
Here the part i can't explain :
when the request of the interceptor (to refresh token) hit the server, it messes with the data of previous request.
I had to add a middleware in django to clear the body when the request fails with 401 and it worked for me. But it is not a proper solution i guess.
Unfortunately the lib is loosely mantained and it's flawed in some aspects.
Eg: concurrent requests are not correctly queued when the request is sent with and invalid token but the response arrives when a new token is already issued.
As is, if you look at the lib source, you'll find in the very first lines:
/** #type {Object} */
const defaults = {
/** #type {Number[]} */
statusCodes: [
401 // Unauthorized
]
};
This means that only 401 code is managed and the statusCodes are not exported so them remains private.
If you want to continue to use this library you can fork it in order to change what does not fit with your stack or simply copy the source, edit it and use it as a local service.
I'm trying to logout my user once they get a 401. I'm using axios to return data from the api
I was looking around and found the same axios.interceptors.response
axios.interceptors.response.use(
response => response,
error => {
const {status} = error.response;
if (status === 401 ) {
store.dispatch('snackBar', snackbarObj)
}
return Promise.reject(error);
}
)
It appears my error.response is undefined. I'm not sure what is wrong? any ideas?
You're not getting a response from the request you're doing with Axios since the browser received a 401 unauthorized response when doing the preflight OPTION request, resulting in a Network Error for the request you're trying to do.
This is related to how CORS works and how your backend handles OPTION requests. To understand how the backend server should handle preflight requests, it's important to understand what is the motivation behind introducing preflight requests.
The backend server should not check for authentication on OPTION requests, it should validate that the request is being made to an endpoint that accepts cross-domain requests and return a success code if it does.
Then, automatically, the browser will proceed with the initially intended request.
That way, the Axios interceptor will receive the 401 error code if the user is no longer authenticated.
Shameless self-promotion, I've published a simple Axios plugin called axios-middleware which helps abstract the use of Axios interceptors in bigger apps. It offers an example of middleware that automatically handles unauthenticated requests by trying to authenticate again before resending the request.
Response object will be undefined also if preflight OPTION request ended successfull, but response for next GET/POST doesn't contain Access-Control-Allow-Origin http-header.
In my case adding Access-Control-Allow-Origin header for nginx 401 response solves problem
For those who still struggling with this, use the following error handling for better control
if (error.response) {
// Request made and server responded
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
return Promise.reject(error);
it is not best practice but i solve it this way
axios.interceptors.response.use(
response => response,
error => {
if (typeof error.response === "undefined") {
// do somthing
}
return Promise.reject(error);
}
)