Angular 5 wont send auth cookie back - asp.net

I have an Angular 5 app and an ASP.net core 2.0 rest api. When they are both running on localhost, my authentication works just fine. I have recently deployed the rest api on another server and suddenly I can no longer authenticate. I have CORS enabled on the server via:
app.UseCors(options =>
{
options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials();
});
In Angular, I setup my request options:
var headers: Headers = new Headers();
headers.append('Content-Type', 'application/json')
var requestOptions = new RequestOptions({
headers: headers,
withCredentials: true,
});
Using fiddler, I watch my login POST request and see that the Response contains:
Set-Cookie: .AspNetCore.Identity.Application=CfD...;expires=Thu, 16 Aug 2018 21:25:02 GMT; path=/; samesite=lax; httponly
However, after the redirect back to the home page, I make an api request to see if a user is logged in
var url = this.runtimeConfig.getConfig().apiUrl + `api/Auth/IsUserLoggedIn/`;
return this.http.get(url, requestOptions).toPromise().then(response => {
return <BoolDTO>ServerMessages.deserialize(response);
});
Looking at fiddler, this GET request does not contain a COOKIE header as it did when both apps were on localhost.
Odder still, if I then paste the url http://<serverip>:<port>/api/Auth/IsUserLoggedIn/ into my browser's address bar, it does send the COOKIE header and says that a user is logged in.
Is there something I need to do to get Angular's http client to store and resend the cookie?

It looks like it was the samesite=lax setting on the cookie. It seems odd that "lax" would restrict this scenario, but the documentation for ASP.net's SameSiteMode.LAX says:
LAX - The cookie will be sent with "same-site" requests, and with "cross-site" top level navigation.
Since XHR requests are not top level navigation, it would not send the cookie. The solution to this is to remove the SameSite from the cookie on the server in ConfigureServices
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
I should mention that doing this opens your site to CSRF attacks, but if you want a client side frontend hosted on a different server than a REST API, this seems to be the only way. I will likely re-work my project so that the front and back end are hosted on the same server.

Related

Cannot set cookie in client from a Go API

I have a backend written in Go, hosted on Heroku, let's call it, https://foo.herokuapp.com. I have a frontend hosted on a different domain, let's call it, https://ui.example.com.
The backend API has an endpoint /api/user/login which sends back a JSON Web Token in the form of cookie shown as below:
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token, // the JWT
HttpOnly: false, // for testing, set to true later once I fix this
MaxAge: int(time.Hour * 24 * 3),
Expires: time.Now().UTC().Add(time.Hour * 24 * 3),
Path: "/",
Secure: true,
SameSite: http.SameSiteNoneMode,
})
These are my CORS settings on the server.
crossOrigin := cors.New(cors.Options{
AllowedOrigins: []string{allowedOrigin},
AllowCredentials: true,
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut},
})
The frontend makes a request to the backend as given below.
const endpoint = "/api/user/login/"
fetch(host + endpoint, {
method: "POST",
credentials: 'include',
body: JSON.stringify({
email,
password
})
}).then((response) => console.log(response))
.catch((err) => console.log(err));
PROBLEM:
Now this cookie is actually visible in my browser's Network tab.
But the cookie does not exist in the application tab (or Storage tab in firefox where cookies exist). The browser is not saving the cookie, which is causing the subsequent requests to fail as the token in the cookie is verified and decoded before processing the actual request.
In another somewhat related thread, I got to know that Heroku terminates SSL before reaching my app. And, thus secure cookies cannot be set for non-SSL traffic. The solution there suggests trusting the scheme found in X-Forwarded-For. I enabled that using using the https://github.com/gorilla/handlers package as follows.
// srv is my actual handler
// crossOrigin is the CORS middleware
// finally wrapped by ProxyHeaders middleware
handlers.ProxyHeaders(crossOrigin.Handler(srv))
Yet this is not working.
I read many threads/blogs. Nothing has worked so far. What am I doing wrong?
Cookies are saved by the browser for the domain which set the cookie in the first place. Only becasue you can't see the cookie in the Application tab, does not mean that the cookie wasn't saved.
If your frontend https://ui.example.com makes an XHR call to https://foo.herokuapp.com and that call returns a Set-Cookie header, then the browser saves that cookie under foo.herokuapp.com domain. You will not see it in the ui.example.com's Application tab. Still, when you make another XHR call to foo.herokuapp.com then the browser will send the cookies that you've set earlier.
You can make this experiment: After logging in, open a new tab and navigate to https://foo.herokuapp.com. Now open the Application tab and you should see your cookies there.
That said, remember that the browser will treat these cookies as 3rd party cookies, and browser vendors will eventually drop support for 3rd party cookies. Eventually you should make sure that your frontend and backend are served from the same parent domain.
As for the other problem - Heroku's termination of SSL between their gateway and your app is not a problem. The secure flag on a cookie is an information for the browser - the browser will not accept or send a cookie with this flag over a non-SSL connection. The connection between your browser and the heroku server is SSL, so cookies will be accepted/sent. In your backend, cookies are just HTTP headers, and the backend does not really care neither about the cookies' flags nor by the connection type.

Session Cookie isn't sent to the server

I try to emulate the access to the webservice of my company using a dart file.
Here is my request where I try to send the jsessionid cookie to the server :
taches = await http.post(
Uri.parse('https://test1.beotic.net/beop3server62/p3servicejson'),
headers: ({
"content-type": "application/json-rpc",
"set-cookie": setcookie
}),
body: body
);
The recuperate the setcookie variable is from the response header of a previous request that I sent to the server with an authentication token, what's inside setcookie look like this : JSESSIONID=01D68460B589F3B34A9C3208FA6CEA51; Path=/beop3server62; Secure
But it seems that the setcookie variable is never sent to the server as the response to this last request always contains a different jsessionid cookie in its headers and the body is :
{"jsonrpc":"2.0","id":"10","error":{"code":0,"message":"The user is
not
authentified","data":{"exceptionTypeName":"com.beotic.apps.p3.exception.AuthentificationException","message":"The
user is not authentified"}}}
I'm quite lost with all this as I tried many different things and none of them seem to work.
Thanks for helping !
Set-Cookie is a response header, not a request header.
Clients should read the Set-Cookie header, use the information in it to store data in their internal cookie jars, then generate Cookie request headers from the cookie jars when making subsequent requests to the same host.

Cross domain state cookie issue for oAuth using firebase functions while on the same domain

I am implementing a oAuth login for a user for the firebase platform.
All works fine except if the user has disabled cross domain cookies.
Here is what I did.
From my domain/app the user gets redirected to a cloud function.
The could function sets the state cookie and redirects the user to the oAuth provider.
The user signs in to the oAuth provider and gets redirected back to another function to get the code etc. And here is the problem
On step 3 above the function cannot read any cookie if the user has disabled the cross domain party cookies from his browser.
Both functions are on the same domain as seen below in the screenshot.
Is there any way I can remedy this issue? Am I doing something wrong in my approach?
I cannot understand why the 2 functions are treated as crossdomain.
Update to include more info
Request:
Request URL: https://europe-west2-quantified-self-io.cloudfunctions.net/authRedirect
Request Method: GET
Status Code: 302
Remote Address: [2a00:1450:4007:811::200e]:443
Referrer Policy: no-referrer-when-downgrade
Request Headers
:authority: europe-west2-quantified-self-io.cloudfunctions.net
:method: GET
:path: /authRedirect
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
cookie: signInWithService=false; state=877798d3672e7d6fa9588b03f1e26794f4ede3a0
dnt: 1
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Response Headers
alt-svc: quic=":443"; ma=2592000; v="46,43,39"
cache-control: private
content-encoding: gzip
content-length: 218
content-type: text/html; charset=utf-8
date: Sat, 03 Aug 2019 08:55:18 GMT
function-execution-id: c8rjc7xnvoy8
location: https://cloudapi-oauth.suunto.com/oauth/authorize?response_type=code&client_id=xxx&redirect_uri=&scope=workout&state=1c8073866d1ffaacf2d4709090ad099872718afa
server: Google Frontend
set-cookie: state=1c8073866d1ffaacf2d4709090ad099872718afa; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
set-cookie: signInWithService=false; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
status: 302
vary: Accept
x-cloud-trace-context: 99a93680a17770f848f200a9e729b122;o=1
x-powered-by: Express
After that and once the user returns from the service he authenticated against the code that parses the cookies (or the function that handles that) is:
export const authToken = functions.region('europe-west2').https.onRequest(async (req, res) => {
const oauth2 = suuntoAppAuth();
cookieParser()(req, res, async () => {
try {
const currentDate = new Date();
const signInWithService = req.cookies.signInWithService === 'true';
console.log('Should sign in:', signInWithService);
console.log('Received verification state:', req.cookies.state);
console.log('Received state:', req.query.state);
if (!req.cookies.state) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.state !== req.query.state) {
throw new Error('State validation failed');
}
console.log('Received auth code:', req.query.code);
const results = await oauth2.authorizationCode.getToken({
code: req.query.code,
redirect_uri: determineRedirectURI(req), // #todo fix,
});
// console.log('Auth code exchange result received:', results);
// We have an access token and the user identity now.
const accessToken = results.access_token;
const suuntoAppUserName = results.user;
// Create a Firebase account and get the Custom Auth Token.
let firebaseToken;
if (signInWithService) {
firebaseToken = await createFirebaseAccount(suuntoAppUserName, accessToken);
}
return res.jsonp({
firebaseAuthToken: firebaseToken,
serviceAuthResponse: <ServiceTokenInterface>{
accessToken: results.access_token,
refreshToken: results.refresh_token,
tokenType: results.token_type,
expiresAt: currentDate.getTime() + (results.expires_in * 1000),
scope: results.scope,
userName: results.user,
dateCreated: currentDate.getTime(),
dateRefreshed: currentDate.getTime(),
},
serviceName: ServiceNames.SuuntoApp
});
} catch (error) {
return res.jsonp({
error: error.toString(),
});
}
});
});
The above code does not find a cookie with the name state
So it fails here
if (!req.cookies.state) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.state !== req.query.state) {
throw new Error('State validation failed');
}
Did a little more search here is some more info.
The example I based on https://github.com/firebase/functions-samples/tree/master/instagram-auth
Looks like other users suffer from the same issue https://github.com/firebase/functions-samples/issues/569
I opened also this issue https://github.com/firebase/firebase-functions/issues/544
Your Response shows a Set-Cookie header for state and signInWithService cookies without a domain attribute:
set-cookie: state=1c8073866d1ffaacf2d4709090ad099872718afa; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
set-cookie: signInWithService=false; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
Set-Cookie without a domain means that what happens to the cookie on the way back to the server is browser-dependent. The "default", spec-compliant behavior: the browser will grab the FQDN of the service URL and associate it with the cookie. RFC6265:
Unless the cookie's attributes indicate otherwise, the cookie is
returned only to the origin server (and not, for example, to any
subdomains)...If the server omits the Domain attribute, the user agent
will return the cookie only to the origin server.
When the browser decides whether to accept the cookie from a HTTP service, one of the decision criteria is if the cookie is first-party or third-party:
First-party cookie: if the resource (web page) you requested that triggered a call to europe-west2-quantified-self-io.cloudfunctions.net/authRedirect resides at https://europe-west2-quantified-self-io.cloudfunctions.net/...
Third-party cookie: if the resource (web page) you requested that triggered a call to europe-west2-quantified-self-io.cloudfunctions.net/authRedirect resides at https://some.domain.app.com/...
In your case the FQDN of your "parent" app/page is likely different from europe-west2-quantified-self-io.cloudfunctions.net, thus these cookies are labeled as third-party. As you found out, a user can choose to block third-party cookies. As of August 2019, Firefox and Safari block 3rd-party cookies by default. Most (if not all) ad blockers and similar extensions also block them. This would lead the browser to simply ignore Set-Cookie header in the HTTP response from europe-west2-quantified-self-io.cloudfunctions.net/authRedirect. The cookie would not be sent back to 2nd Firebase function at europe-west2-quantified-self-io.cloudfunctions.net/authToken because it doesn't exist on the client.
Your options:
Host your app and Firebase functions on the same domain.
An architecture where all HTTP requests (app and Firebase functions) flow through the app; the latter acts as a proxy of sorts for the function calls. Here's one way to do it in Firebase.
Let's say your app and Firebase functions do reside in different domains. In Javascript you can create a small piece of middleware that calls the /authRedirect FB function, parses the response (incl. the cookies via Set-Cookie header), then writes the response (incl. cookies) back to the browser via document.cookie. The cookies in this case would be first-party.
Don't use cookies at all. The oAuth authorization grant flow that you're doing against cloudapi-oauth.suunto.com as the authorization server does not require cookies. You followed an instagram-auth example that recommends this flow
When clicking the Sign in with Instagram button a popup is shown which
redirects users to the redirect Function URL.
The redirect Function then redirects the user to the Instagram OAuth
2.0 consent screen where (the first time only) the user will have to grant approval. Also the state cookie is set on the client with the
value of the state URL query parameter to check against later on.
The check against state query parameter is based on an implementation best practice for oAuth clients when authorization servers don't support the PKCE extension (cloudapi-oauth.suunto.com doesn't support it):
Clients MUST prevent CSRF. One-time use CSRF tokens carried in the
"state" parameter, which are securely bound to the user agent, SHOULD
be used for that purpose. If PKCE [RFC7636] is used by the client and
the authorization server supports PKCE, clients MAY opt to not use
"state" for CSRF protection, as such protection is provided by PKCE.
In this case, "state" MAY be used again for its original purpose,
namely transporting data about the application state of the client
The key phrase is securely bound to the user agent. For web apps, a cookie is a decent option of implementing this binding but it's not the only option. You can stick the value of state into local or session storage, single-page apps do exactly that in practice. If you want to live in the cloud, you can stick state in Cloud Storage or equivalent...but you'd have to manufacture a key that uniquely identifies your client and this particular HTTP request. Not impossible but perhaps overkill for a simple scenario.

HERE-maps CORS problems with Autocomplete

I want to use HERE maps autocomplete in my project.
But when a i send request like the one in documentation
this.axios.get('http://autocomplete.geocoder.api.here.com/6.2/suggest.json
?app_id={YOUR_APP_ID}
&app_code={YOUR_APP_CODE}
&query=Pariser+1+Berl
&beginHighlight=<b>
&endHighlight=</b>'
)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
})
i get an error
OPTIONS http://autocomplete.geocoder.api.here.com/6.2/suggest.json?{...} 405
Access to XMLHttpRequest at 'http://autocomplete.geocoder.api.here.com/6.2/suggest.json?{...}' 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.
In Chrome developer console in network panel i check this
Provisional headers are shown
Access-Control-Request-Headers: x-auth-token, content-type
Access-Control-Request-Method: GET
I set content-type in request headers to application/json and Provisional headers changed
to Access-Control-Request-Headers: x-auth-token
Access-Control-Request-Method: GET
So if i understand right, i should set x-auth-token header. But where can i take this token?
Or may be this problem has another reason?
There's nothing about such problems in documentaion.
The problem was simple and a bit stupid.
When user authenticated in my app I added default header to axios
axios.defaults.headers.common['X-Auth-Token'] = token
so this header was sended to all requests.
But HERE-map API doesn't want this header in requests and this was the cause of the problem.
The solution was to remove this header from requests to HERE-map API.
For those who have defined by default the header :
'Content-Type': 'application/json'
You must deactivate it, there should not be any HttpHeaders on the call request to Here API services.
temporary install Allow-Control-Allow-Origin google chrome plugin .. installed then you can show top right side click on that and switch the button then refresh then again call your api and get the response.

ASP.NET MVC Website Partial SSL Authentication cookie not submitted in request

I'm trying to make a POC of which is possible to have a website that uses http and https. So i have a control in my master page that needs info if the user is authenticated or not. For this I want to use HttpContext.Current.User.Identity.IsAuthenticated. If is authenticated shows info for authenticated users, if not appear the login control.
To authenticate the control make an AJAX POST request to the Login action that has the [RequireHttps] attribute. The URL used in the AJAX request is:
$.ajax({
type: 'POST',
url: '#Url.Action("ModalLogIn", "Authentication", null, "https", Request.Url.Host + ":44300")',
By the way I'm using VS2013 IIS express with SSL enabled.
As you can see in my AJAX request i'm using the HTTPS in action url.
The request is made to the server using SSL and the response is made with success.
The problem is that in the subsequent requests the ASPXAUTH cookie is not passed in the request header. So the server does not get the user authentication info. The subsequent requests are made with no SSL, are simple HTTP requests.
I know that in security terms the authentication is still insecure because i'm expecting to pass the ASPXAUTH through HTTP, but like I said is a POC and I want to see if it is possible to make a simple authentication request using HTTPS and all the others using HTTP.
As requested this is the Response Headers:
Access-Control-Allow-Orig... *
Cache-Control private
Content-Length 15
Content-Type application/json; charset=utf-8
Date Sat, 26 Oct 2013 18:57:55 GMT
Server Microsoft-IIS/8.0
Set-Cookie ASP.NET_SessionId=j2a53htev0fjp1qq4bnoeo0l; path=/; HttpOnly
ASP.NET_SessionId=j2a53htev0fjp1qq4bnoeo0l; path=/; HttpOnly
IAC.CurrentLanguage=en; expires=Sun, 26-Oct-2014 19:57:55 GMT; path=/
.ASPXAUTH=730DEDBFD2DF873A5F2BD581AA0E25B685CAD12C26AEA63AD82484C932E26B617687A05BB403216CC5EFCF799970810059F9CA2CF829F953580AF81FF48102003C0129AB04424F0D011A733CAAF1DE00688E5A4C93DEA97338DD2B5E7EE752F3761A470D52449BEBCA74098912DE37AA8C1E293B1C5D44EB1F9E9384DAAEF289; path=/; HttpOnly
X-AspNet-Version 4.0.30319
X-AspNetMvc-Version 3.0
X-Powered-By ASP.NET
X-SourceFiles =?UTF-8?B?QzpcTXkgRGF0YVxCaXRidWNrZXRcaWFjLXdlYnNpdGVcaW1wbGVtZW50YXRpb25cZG90bmV0XElBQy5XZWJcQXV0aGVudGljYXRpb25cTW9kYWxMb2dJbg==?=
It might be that when you set the auth cookie, it is marked as "Secure".
Using the Chrome Developer Tools, click on 'Resources', then cookies. Under the 'Secure' column check if the cookie is marked. If it is, then this means that the browser will not send the auth cookie using a non-secure connection.
Just a shot in the dark, but try setting the ASPXAUTH cookie with an expiration date.
It's possible that the browser, upon receiving a session cookie, will only present the session cookie on connections using the same protocol (https) as when it was set. I know for sure that persistent cookies do not have this limitation.
Also, investigate whether port could be the issue. If your AJAX goes over 44300 and your web goes over 80 or 443, it's possible the cookie is lost because the browser considers secure cookies to be port-specific. The W3C spec doesn't say whether cookies are private with respect to port; browsers vary.
All things work perfect like that ajax request in HTTPS manner by JS. Related respond works correctly too. But it seems that you have not prepared Login page in SSL too! My meaning is :
[RequireHttps]
public ActionResult Login()
{
return View();
}
Then Send request to HttpPost enabled Action. I believe that will work correctly. Unless you had some lack of requirements like MicrosoftMvcAjax.js and MicrosoftAjax.js in situations that you are using formal Microsoft ajax form by your ViewEngine (Perhaps by Razor). I think studying this Article can help you more.
Good Luck.

Resources