I have an api built in FastAPI running on localhost:8000 and a NextJS frontend running on localhost:3000. I'm using HttpOnly cookie to store the JWT token after user authentication. But for some reason the cookie is not set in chrome and subsequent requests are not authenticated. When I test the mechanism in insomnia it is working, but on the frontend it doesn't. Here is my actual setup.
The login endpoint:
#router.post("/signin", response_model=SigninResponseSchema)
def sign_in(
response: Response,
*,
user_credentials: SigninSchema,
db: Session = Depends(database.get_db),
) -> any:
signin_infos = auth_service.authenticate_user(db, user_credentials=user_credentials)
sign_in_response = SigninResponseSchema(
status="successful",
access_token=signin_infos["access_token"],
user_data=signin_infos["user_data"],
user_roles=signin_infos["user_roles"]
)
token = jsonable_encoder(sign_in_response.access_token)
response.set_cookie(
"Authorization",
value=f"Bearer {token}",
httponly=True,
secure=True,
samesite="none",
max_age=1800,
expires=1800,
)
return sign_in_response
The cors setup:
origins = [
"http://localhost:3000"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "HEAD", "OPTIONS"],
allow_headers=["Access-Control-Allow-Headers", "Content-Type", "Authorization", "Access-Control-Allow-Origin","Set-Cookie"],
)
How do I solve the problem? I've already taken a look to several solutions on the internet but none of them is working. What could be the problem ?
Your setup on the FastAPI side looks good, the issue is likely in your front-end application.
One thing that jumps out is that you're making your requests from http://localhost:3000 which isn't over HTTPS, and you're setting your cookie with secure=True. Ideally you should be running your front-end app locally over HTTPS and making requests from https://localhost:3000.
Here's a couple more things to check on the client side:
In your request, make sure you've set the XMLHttpRequest.withCredentials flag to true (this was my problem), this can be achieved in different ways depending on the request-response library used:
jQuery 1.5.1 xhrFields: {withCredentials: true}
ES6 fetch(): credentials: 'include'
axios: withCredentials: true
Your request headers should include "Access-Control-Allow-Origin": "http://localhost:3000"
Credit: This answer to a similar question got me unstuck from a similar issue
Related
I am currently using the FLASK developer HTTP server, and I am trying to build a local service (run on localhost) that serves files for a remote visualization website.
Here is the code for the python side
#app.route('/task/<path:path>', methods=['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'])
def static_file1(path):
p = "./task/" + path
return flask.send_file(p, conditional=True)
For safari, it just works like a charm.
As this screenshot indicates, flask development http server can serve files partially.
However, it didn't work for firefox for one request but not for the other.
And here are the headers for the first failed request
Successful request header
So I do believe the CORS header (Access-Control-Allow-Origin) is set correctly, otherwise the second request would fail.
Then what did I do incorrectly?
Second part:
It also doesn't work in Chrome, both requests failed, but I found the article below explaining new security features:
https://developer.chrome.com/blog/private-network-access-preflight/#:~:text=%23%20What%20is%20Private%20Network%20Access,to%20make%20private%20network%20requests.
But even with "Access-Control-Allow-Private-Network" set to "true" (See screenshot above), both requests still failed in chrome. And error msg:
Access to XMLHttpRequest at 'http://localhost:10981/task/a5c8616777d000499ff0cd5dbb02c957/datahub.json' from origin 'https://somepublic.website' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.
Any suggestion would be helpful!
Thanks!
Update 1:
After enabling ad-hoc SSL context (unsigned certificate) on the flask side, and using https on both localhost and "the public website", and changing the "#allow-insecure-localhost" flag in chrome to true, it works in chrome now. But still doesn't in firefox.
If you check the specification you will see that it is a "Draft Community Group Report" and
This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track.
The contribute list is made up entirely of people working for Google.
I can't find any mention of it in Firefox's bug tracker.
It looks like this is a highly experimental specification, which Firefox simple doesn't implement.
There doesn't appear to be any way to persuade Firefox to provide access from a secure, public Oritin to an insecure private origin.
I build a web application that embeds a Qlik Sense iframe, which means an iframe from an external server. This is how the fetch call looks like:
return fetch("https://servername/prefix/qrs/about?xrfkey=<key>", {
method: 'GET',
mode: 'cors',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Qlik-xrfkey': '<key>',
'Authorization': 'Bearer ' + token
},
}).then(response => {
return response.status === 200;
});
For authentication to the Qlik iframe I'm using the JWT, which I register in Qlik Sense. As a reply I get a response header "Set-Cookie" and the according session token. However Chrome seems to block the cookie with the warning "This Set-Cookie was blocke due to user preferences". It works in Edge and Firefox.
Since I'm developing, I'm using "localhost:8080". I read that Chrome ignores cookies on http://localhost, so I've tried using SSL -> "https://localhost:8080" which didnt work. Additionally, I've tried via IP, so "https://127.0.0.1:8080", didnt work either.
Moreover, I configured the backend (Qlik Management Console) such that SameSite = None and that it uses the "Secure" attribute, since I read that this might cause issues as well. Plus I'm using the
credentials 'include' attribute.
These are my request headers
Request headers
and here are the response headers
Response headers
Does anyone of you have an idea how to fix this for chrome? Do you need additional information?
Thanks in advance!
Cheers
Okay found the solution. If anyone faces the same issue: I always used the incognito Chrome to avoid caching issues. And there was a setting that blocked all 3rd party cookies in incognito mode by default:
Chrome 3rd party cookies incognito mode
Once you change that to "accept", it works. Cheers
I have a headless application written in yii, with an Angular application using the yii2 api. Currently im using local storage for tokens, but I read this link and would like to store the token in a cookie.
Auth action:
\Yii::$app->response->cookies->add(new Cookie([
'name' => 'token',
'value'=> $token->__toString()
]));
AuthMethod:
if (($cookie = $cookies->get('token')) !== null) {
die('Token found in cookie');
$token = $parser->parse($cookie->value);
}
The token is allways null, so it seems like cookies are disabled by default in Rest controllers / JSON responses, how can I enable this?
For furture reference, if the link is dead it concludes that cookies are better than local storage for JWT tokens
Cookies, when used with the HttpOnly cookie flag, are not accessible through JavaScript, and are immune to XSS. You can also set the Secure cookie flag to guarantee the cookie is only sent over HTTPS. This is one of the main reasons that cookies have been leveraged in the past to store tokens or session data. Modern developers are hesitant to use cookies because they traditionally required state to be stored on the server, thus breaking RESTful best practices. Cookies as a storage mechanism do not require state to be stored on the server if you are storing a JWT in the cookie. This is because the JWT encapsulates everything the server needs to serve the request.
EDIT
Using the native PHP $_COOKIE the cookie can be read by the yii2 application, but the setcookie() does not work. It looks like the yii2-rest controller strips away the headers before sending the response.
I will make my first attempt to answer and I hope it makes sense and helps you and others.
I asked you about angular version, because, as you know angular works as a one page app, and I will need proof of concept code to show my point «I will asume angularjs 1.6.x», it means it works away from your YII2, unless you are rendering angular on call. Now there are two ways to “bypass” this in order to set up the cookie.
The first one will be to set up your cookie inside angular, on your controller call your login endpoint and make it return the token (in my example I am not doing that part just for speed). And then build the cookie using the $cookie service «you will have to import angular-cookie».
The other way could be to call and endpoint inside Yii «look at the actionSetCookie example» and build the cookie inside it, I don't like that one that much looks dirty to me.
Now here comes the big problem, you may have, yii uses a cookie validation that “signs the cookie”, but when you build the cookie outside, this cookie will not work. So… to make this example work you will have to turn off cookie validation.
components' => [
'request' => [
'enableCookieValidation' => false, //<---make it false
'enableCsrfValidation' => true,
'cookieValidationKey' => 'hdfghdsrthrgfdhfghthdrtth',
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
...
]
Now, I made a working test.
First I made two simple action to test that I was actually building the cookie. And that I was able to call them with postman.
public function actionSetCookie()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$session = Yii::$app->session;
$cookies_rsp = Yii::$app->response->cookies;
$cookies_rsp->add(new Cookie([
'name' => 'token',
'value' => 'tt-eyJhbGciOiJIUzI1NiIs...',
]));
$response = ['cookie set'=>$cookies_rsp];
return $response;
}
public function actionCookie()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$session = Yii::$app->session;
$cookies = Yii::$app->request->cookies;
$token = '';
if (isset($cookies['token'])) {
$token = $cookies['token']->value;
}
$response = ['token'=>$token];
return $response;
}
And I made two controllers in angular to make the explanation:
.controller('View1Ctrl', ['$cookies', function ($cookies) {
console.log('View1Ctrl++++++++');
var vm = this;
vm.title = 'Customers';
$cookies.remove('token');
$cookies.put('token', 'my-angular-token');
vm.myToken = $cookies.get('token');
console.log('myToken', vm.myToken);
}
.controller('View2Ctrl', ['$http', function ($http) {
console.log('View2Ctrl');
//Lest set the cookie
$http({
method: 'GET',
url: 'http://test.dev/site/set-cookie'
}).then(function successCallback(response) {
console.log('response', response);
}, function errorCallback(response) {
console.error('err', response)
});
}
Now here it goes
On solution 1 «View1Ctrl», I build the cookie using the $cookie service, and I can validate it using action actionCookie, this will be read only if enableCookieValidation is false.
On the solution 2 «View2Ctrl», I am using the actionSetCookie as a http get endpoint that does not much, but sets the cookie, this cookie will work with enableCookieValidation false or true.
Conclusión
Remember that angular and yii2 are "supposed" to be agnostic and independent, so you will have to consume yii as endpoint.
You will have to se the enableCookieValidation, depending on your solution. I am not sure if there is a way to do it with angular but probably is not a good idea because you will have to publish the cookieValidationKey inside angular.
I don't like using cookies for apis, personally, because the idea of stateless and cookieless will help if your are developing a mobile app «I might be wrong about that».
About postman, it will work with solution 2 unless you turn off enableCookieValidation. Remember that this validation will add some salt to the token inside a cookie, that is yii additional security.
Finally on p[ostman, if enableCookieValidation is set to true and toy are manually making the cookie, Yii will not receive the cookie, because of security.
Just to illustrate this security related to the signing of cookies, I captured this video. I hope this will help. So this is because CookieValidation is true. This is a reason no to use the PHP default cookie service but tu use the one that Yii provides. On the video you will see how this system very specific for each cookie. ANd why you may not see the cookie in postman.
If you make CookieValidation false, this manual cookies, php default cookies will actually work again, but is less secure.
Video
About the discussion on the blog and Angular, remember that angular actually protects your app when using $http calls, it is very secure in that sense, but also don't forget to use ngSanitize on your app.
More on this:
https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection
https://docs.angularjs.org/api/ngSanitize
https://docs.angularjs.org/api/ngCookies/service/$cookies
Finally, if I find something on securing the cookie like Yii2 does from angular I will add that to the post.
Hope it helps.
Just in case you like to look at my code
https://github.com/moplin/testdev
If you want to access cookies on server side so you have to send them as well in your request. Angular by default does not send cookies in XHR request. To enable cookies in request add this following code:
.config(function ($httpProvider) {
$httpProvider.defaults.withCredentials = true;
});
OR
$http.get("URL", { withCredentials: true })
.success(function(data, status, headers, config) {})
.error(function(data, status, headers, config) {});
See Usage section in angular $http
Make sure you also setting that cookie on client side, using javascript and then check in your chrome console for http request making sure it's also sending cookie in it.
Make sure you read cookies from $cookies = Yii::$app->request->cookies;
http://www.yiiframework.com/doc-2.0/guide-runtime-sessions-cookies.html#reading-cookies
And writing a cookie using $cookies = Yii::$app->response->cookies;
http://www.yiiframework.com/doc-2.0/guide-runtime-sessions-cookies.html#sending-cookies
If the token is an access token you could you use in REST controller
the autheticator behaviors
yii\filters\auth\HttpBasicAuth;
yii\filters\auth\HttpBearerAuth;
yii\filters\auth\QueryParamAuth;
and when received first time token to save it to a cookie in the browser from angular
BACKGROUND:
I have an Http handler which receives SIM/CDMA numbers ('8953502103000101242') in json string format from jQuery Ajax call using http POST method to activate them on server.
THE PROBLEM:
On Local Development Environment I can send up to 65K SIMS' Numbers (8953502103000101242 key/value pair in json string) BUT when I deploy to LIVE server then I faced the following problem.
If I send 2000 SIMS numbers separated by comma to Http handler then Http handlers receives http request successfully But when I send more than that (3000, 4000, 5000 SIM numbers in json string) then http request doesn’t reach to HttpHandler at server even after few hours.
If I send 5000 sim numbers in http POST using jquery then the total size request sent is 138.0 KB. and it should be passed to server because server maxRequestLength is 2048576. but it is not being sent to server when we deploy it on live server and it work fine on local development environment.
TRIED SOLUTION:
I tried to resolve the problem by editing httpRuntime configuration in web.config as follows
By using above httpRuntime configuration I noticed in Firefox firebug net state that It keeps waiting sending the request as executionTimeout="7200" but if executionTimeout="600" then it returns timeout error.
ITENDED SOLUTION:
I think if I try to sync above httpRuntime element in Machine.config file as well then it work fine.
REQUIRED SOLUIOTN
WHAT CAN THE BE PROBLEM AND HOW TO RESOLVE IT. PLEASE SUGGEST IN THIS REGARD. WAITING FOR A QUICK SOLUTION.
Jquery call to HttpHandler is as follows:
$.ajax({
type: "POST",
url: "../../HttpHandlers/PorthosCommonHandler.ashx",
data: { order: JSON.stringify(orderObject), method: "ValidateOrderInput", orgId: sCId, userId: uId, languageId: lId },
success: function(response) {
var orderResult = new dojo.data.ItemFileReadStore({
data: {
jsId: "jsorderResult",
id: "orderResult",
items: response.Data
}
});
});
UPDATE
I have diagnosed the problem. one problem was executionTimeout configuration in web.config. and the after making this change second problem was due to long operation time the request was being interrupted by internet (network communication). I made sure I have reliable internet connectivity and tested it again and it worked.
BUT Now i am facing another problem. My httphandler send request to a webservice and I am getting following exception in response.
The operation has timed out
I have fixed the problem.
one problem was executionTimeout configuration in web.config. and the after making this change second problem was due to long operation time the request was being interrupted by internet (network communication). I make sure I have reliable internet connectivity and tested it again.
configured the webserivces as follows.
<httpRuntime executionTimeout="7200" enable="true" maxRequestLength="2048576" useFullyQualifiedRedirectUrl="false" />
and
[WebMethod(Description = "Delete template",BufferResponse = false)]
Specifying "BufferResponse=false" indicates that .NET should begin sending the response to the client as soon as any part of the response becomes available, instead of waiting for the entire response to become available.
I am a novice to Angularjs and tried to follow example given for $http.get on angularjs website documentation.
I have a REST service, which when invoked returns data as follows:
http://abc.com:8080/Files/REST/v1/list?&filter=FILE
{
"files": [
{
"filename": "a.json",
"type": "json",
"uploaded_ts": "20130321"
},
{
"filename": "b.xml",
"type": "xml",
"uploaded_ts": "20130321"
}
],
"num_files": 2}
Part of the contents of my index.html file looks like as follows:
<div class="span6" ng-controller="FetchCtrl">
<form class="form-horizontal">
<button class="btn btn-success btn-large" ng-click="fetch()">Search</button>
</form>
<h2>File Names</h2>
<pre>http status code: {{status}}</pre>
<div ng-repeat="file in data.files">
<pre>Filename: {{file.filename}}</pre>
</div>
And my js file looks as follows:
function FetchCtrl($scope, $http, $templateCache) {
$scope.method = 'GET'; $scope.url = 'http://abc.com:8080/Files/REST/v1/list?&filter=FILE';
$scope.fetch = function() {
$scope.code = null;
$scope.response = null;
$http({method: $scope.method, url: $scope.url, cache: $templateCache}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
}).
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
But when I run this, I do not see any result for filenames and I see http status code = 0
When I run , http://abc.com:8080/Files/REST/v1/list?&filter=FILE in browser, I still can see desired results (as mentioned above)
I even tried to debug using Firebug in firefox, I see the above URL gets invoked when I hit "Search" button but response looks to be empty. And interestingly in Firebug under URL, it shows
OPTIONS "Above URL"
instead of
GET "Above URL"
Can you please let me know, what I am doing wrong and why I am not able to access JSON data ?
Thanks,
This is because how angular treats CORS requests (Cross-site HTTP requests). Angular adds some extra HTTP headers by default which is why your are seeing OPTIONS request instead of GET. Try removing X-Requested-With HTTP header by adding this line of code:
delete $http.defaults.headers.common['X-Requested-With'];
Regarding CORS, following is mentioned on Mozilla Developer Network:
The Cross-Origin Resource Sharing standard works by adding new HTTP
headers that allow servers to describe the set of origins that are
permitted to read that information using a web browser.
I have been having the issue using $resource, which also uses $http.
I noticed that when I used AngularJS 1.0.6 the request would not even show up in Firebug, but when using AngularJS 1.1.4 Firebug would show the GET request and the 200 OK response as well as the correct headers, but an empty response. In fact, the headers also showed that the data was coming back as shown by the "Content-Length" header having the correct content length, and comparing this against a REST Client plugin I was using that was successfully retrieving the data.
After being even further suspicious I decided to try a different browser. I had originally been using Firefox 16.0.1 (and also tried 20.0.1), but when I tried IE 9 (and AngularJS 1.1.4) the code worked properly with no issues at all.
Hopefully this will help you find a workaround. In my case, I noticed that I never had this problem with relative URLs, so I'm changing my app around so that both the app and the API are being served on the same port. This could potentially be an AngularJS bug.
I had the same problem today with firefox. IE worked fine. I didn't think it was cors at first because like you I got no errors in the console and got a status of 0 back in my error method in angular. In the firefox console I was getting a 200 response back in my headers and a content length, but no actual response message. Firefox used to give you a warning about cross site scripting that would point you in the right direction.
I resolved the issue by setting up cors on my api. This is really the best way to go.
If you are only using GET with your api you could also try using jsonp this is built right into angular and it is a work around for cors when you do not control the api you are consuming.
$http.jsonp('http://yourapi.com/someurl')
.success(function (data, status, headers, config) {
alert("Hooray!");
})
.error(function (data, status, headers, config) {
alert("Dang It!");
});
It's cross-site-scripting protection.
Try starting google chrome with --disbable-web-security (via command line).
If that isn't working also try to put your angular stuff into an http server instead of using the file protocol. (Tip: use chrome canary if you want to have a browser dedicated to --disable-web-security - of course you'll have to set the command line argument too, but both chrome versions run simultaneously). For release you'll have to set some http headers on the server providing the AngularJS-stuff to allow access to the twitter api or whatever you want to call.