Silent Renew using oidc-client in case SameSite=Lax - identityserver4

Chrome plans to implement the new secure-by-default model for cookies with Chrome 80 in February 2020.
Now I am testing all our applications that might be affected.
For authentication/authorization we use oidc-client (on UI) and IndentityServer on backend.
The workflow is the following:
request:
POST http://my_identity_server/api/authenticate
{userName, password}
response:
Set-Cookie: idsrv=abc
{"redirectUrl":"http://my_identity_server/connect/authorize/callback?client_id=MyApplication&response_type=id_token token&scope=openid"}
request
http://my_identity_server/connect/authorize/callback?client_id=MyApplication&response_type=id_token token&scope=openid"
Cookie: idsrv=abc
response:
302 Location: http://myApplication/#id_token=123&access_token=456&token_type=Bearer
request
http://myApplication/#id_token=123&access_token=456&token_type=Bearer
When application is loaded, Oidc-client adds IFrame to do silentRenew that actually sends additional requests
GET http://my_identity_server/connect/authorize?client_id=MyApplication&response_type=id_token token&prompt=none
Cookie: idsrv=abc
What I don't understand why this last requests indide IFrame include idsrv cookie? The request is done inside an IFrame and it's definitely a third-party cookie.
If I switch SameSite setting to "Strict" everything works as expected, cookies are not send.
If someone can explain me the case or suggest some ideas to check, would be nice! thank you!

OIDC client library uses OIDC session management to track of the session which is done by using OP iframe and client(RP) iframe.
Identity server issues idsrv:session cookie for this purpose
check this
https://medium.com/#piraveenaparalogarajah/openid-connect-session-management-dc6a65040cc

Related

How to allow express backend REST API to set cookie in a react frontend which is using axios?

Backend
I am trying make a JWT cookie based authentication work. I am currently performing the following cookie setting as part of a login route in the backend API.
res.cookie('authCookie', token, {maxAge: 900000, httpOnly: true});
Later when I am auth(ing) any other requests, I am reading off of this cookie and testing it in a passport-jwt strategy.
I have gotten this work in postman - when I perform a login and access a secured route - it works perfectly fine + the cookie is also getting set in postman.
Frontend
Now, I am performing the following call stack in the frontend just to test the working,
axios.post("http://localhost:3001/login", logInParams, config)
.then(result => {
// User is logged in so push them to the respective dashboard
console.log(result);
axios.get("http://localhost:3001/user/profile")
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
return;
})
})
.catch(err => {
console.log(err)
return;
});
So basically, I log the user in and that works perfectly fine - I am getting a JSON response as intended, but the call is supposed to set a cookie which it is not and hence the next axios.get call is not returning successfully. Though for some reason I see session cookie and a CSRF cookie. Only this authCookie or the jwt-cookie is not getting set.
Some extra details
I am using cors with default parameters - could this be an error of this? Or is there any changes I have to do with axios? I have seen some answers and being new to MERN I don't really understand them. Does someone know about this or have experienced it and solved it?
Are you running the server from a different port than the one that provides the client (i.e: webpack-dev-server running on localhost:3000 and Express server on localhost:3001)? This looks like a same-site cookie issue. In the latest versions of some browsers such as Chrome cookie setting is being blocked when this one comes from a different origin site. This is due to security concerns; you can learn more about same-site cookies here.
The change made in the browsers is related to the default value they give to a cookie policy property called same-site. The old workaround was treating all the cookies from a different origin as None by default, but last year it changed to consider the same-site policy as Lax when no same-site policy was not explicitly provided by the server. This is a desirable behaviour from the browser because it helps at preventing third party sites making use of the cookie provided by the server, but you can edit it by different ways:
Changing the default same-site policy of your browser settings (the article about same site cookies explains).
Sending a same-site:'None' from the server. Express has a way to do so explaind on its docs. Keep in mind browsers also have a new policy to ignore same-site:'None' when the cookie is not marked as Secure, what demands the use of HTTPS (I guess this behaviour can be edited in your browser settings if you want to check while using HTTP).
Obviously, any strategy that demands the users to change their browser settings is a no-go, so running HTTPS with Secure cookies is mandatory for same-site:'None'.
You always have the approach of making both browser and client same origin, so you won't have any issues at all with same-site (i.e. the Express server returning the index.html of the production build of your client as its main static). I haven't found any way to configure CORS module to have a default same site cookies policy (or its sole use as middleware to change it), likely not its purpose, but you can try by adding a dynamic origin config.
As far as I've seen, Postman does not support the same-site cookie property yet, so that would explain why is it working on Postman but not on the browser.
From the looks of it - it seems to be an issue with how cors works and I am adding the following answer to help anyone else coming across it. Thank me later :)
Server Side
You will have a cors in your server that looks like this,
app.use(cors());
You will have to set credentials to true and set the allowedHeaders and origin as follows,
app.use(cors({
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
origin: ['http://localhost:3000']
}));
This is because normally cookies are not allowed to be set in the browser if the server and the client are in the same port. To handle this the above is required on the server side.
Client Side
We also have to pass the cookies when we are sending the request and to do this with axios just add the following in the index.js of your react app as so,
axios.defaults.withCredentials = true;
I think you should write send('cookies are set') at the end in res.cookie('authCookie', token, {maxAge: 900000, httpOnly: true});

Prevent my React / Gatsby contact form from being hijacked

I've a Gatsby (React) page with a contact-form which sends the params to an API endpoint.
The form is on the browsers client side.
That Api Endpoint sends to an Email service provider, so far so good.
BUT how can I prevent people from sending emails directly to that endpoint /api/contact-form, in my contact-form I have a ReCaptcha to do that, but the API endpoint is not "secured".
First I thought I can do that with a "host"-check... but the page is on the client side...
Is it the right approach to create a token, when the page is delivered to the client, and check it then against on the API endpoint?
I assume you're talking about CSRF token. It is definitely one way to prevent CSRF attacks. The other option could be setting cors to allow only specific origins to access your API endpoints.

Why is the TLS client certificate not being included in preflight request on most browsers?

I'm having an issue with a web app I'm building. The web app consists of an angular 4 frontend and a dotnet core RESTful api backend. One of the requirements is that requests to the backend need to be authenticated using SSL mutual authentication; i.e., client certificates.
Currently I'm hosting both the frontend and the backend as Azure app services and they are on separate subdomains.
The backend is set up to require client certificates by following this guide which I believe is the only way to do it for Azure app services:
https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth
When the frontend makes requests to the backend, I set withCredentials to true — which, [according to the documentation][1], should also work with client certificates.
The XMLHttpRequest.withCredentials property is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates. Setting withCredentials has no effect on same-site requests.
Relevant code from the frontend:
const headers = new Headers({ 'Content-Type': 'application/json' });
const options = new RequestOptions({ headers, withCredentials: true });
let apiEndpoint = environment.secureApiEndpoint + '/api/transactions/stored-transactions/';
return this.authHttp.get(apiEndpoint, JSON.stringify(transactionSearchModel), options)
.map((response: Response) => {
return response.json();
})
.catch(this.handleErrorObservable);
On Chrome this works, when a request is made the browser prompts the user for a certificate and it gets included in the preflight request and everything works.
For all the other main browsers however this is not the case. Firefox, Edge and Safari all fail the preflight request because the server shuts the connection when they don't include a client certificate in the request.
Browsing directly to an api endpoint makes every browser prompt the user for a certificate, so I'm pretty sure this is explicitly relevant to how most browsers handle preflight requests with client certificates.
Am doing something wrong? Or are the other browsers doing the wrong thing by not prompting for a certificate when making requests?
I need to support other browsers than Chrome so I need to solve this somehow.
I've seen similar issues being solved by having the backend allow rather than require certificates. The only problem is that I haven't found a way to actually do that with Azure app services. It's either require or not require.
Does anyone have any suggestions on how I can move on?
See https://bugzilla.mozilla.org/show_bug.cgi?id=1019603 and my comment in the answer at CORS with client https certificates (I had forgotten I’d seen this same problem reported before…).
The gist of all that is, the cause of the difference you’re seeing is a bug in Chrome. I’ve filed a bug for it at https://bugs.chromium.org/p/chromium/issues/detail?id=775438.
The problem is that Chrome doesn’t follow the spec requirements on this, which mandate that the browser not send TLS client certificates in preflight requests; so Chrome instead does send your TLS client certificate in the preflight.
Firefox/Edge/Safari follow the spec requirements and don’t send the TLS client cert in the preflight.
Update: The Chrome screen capture added in an edit to the question shows an OPTIONS request for a GET request, and a subsequent GET request — not the POST request from your code. So perhaps the problem is that the server forbids POST requests.
The request shown in https://i.stack.imgur.com/GD8iG.png is a CORS preflight OPTIONS request the browser automatically sends on its own before trying the POST request in your code.
The Content-Type: application/json request header your code adds is what triggers the browser to make that preflight OPTIONS request.
It’s important to understand the browser never includes any credentials in that preflight OPTIONS request — so the server the request is being sent to must be configured to not require any credentials/authentication for OPTIONS requests to /api/transactions/own-transactions/.
However, from https://i.stack.imgur.com/GD8iG.png it appears that server is forbidding OPTIONS requests to that /api/transactions/own-transactions/. Maybe that’s because the request lacks the credentials the server expects or maybe it’s instead because the server is configured to forbid all OPTIONS requests, regardless.
So the result of that is, the browser concludes the preflight was unsuccessful, and so it stops right there and never moves on to trying the POST request from your code.
Given what’s shown in https://i.stack.imgur.com/GD8iG.png it’s hard to understand how this could actually be working as expected in Chrome — especially given that no browsers ever send credentials of any kind in the preflight requests, so any possible browsers differences in handling of credentials would make no difference as far as the preflight goes.

IE11 overrides Bearer authorization header in intranet environment

I'm encountering a pretty strange issue in IE11 where the browser is overriding the Authorization header in my requests even though I am setting it via AngularJS.
Basically, I have an HTTP interceptor registered for all requests that looks like this:
AuthInterceptorService.request = function (config) {
config.headers.Authorization = "Bearer " + bearerToken;
}
This works great in all browsers (even IE under certain conditions). I have my app set up in IIS as allowing anonymous authentication and I have basic/integrated authentication disabled for this subsite, however, the parent configuration has windows authentication eabled.
What is happening occasionally is that the browser will make a request to the root URL for a static file (say, /favicon.ico). This request is denied with a 401. The browser responds with negotiated authentication and gets the favicon. At this point, all other browsers still let my code set the Authorization header, but once this integrated authentication happens in IE, the authorization header seems to get stuck - no matter what my code does, the authorization header is always using integrated authentication. This causes all requests to my API to fail because no Bearer token is present.
I was able to work around the favicon issue by specifying a more local favicon (where static files can be served anonymously), but I am wondering if there is a less hacky solution to this issue. Can I somehow convince IE to let me set the Authorization header even if Windows authentication has taken place on a previous request?
Note: I found this question which seems to be related (maybe the same underlying cause).
If you look at the Negotiate Operation Example of the RFC 4559 document, it involves a pseudo mechanism used by IE to negotiate the choice of security when authenticating with IIS.
The first time the client requests the document, no Authorization
header is sent, so the server responds with
S: HTTP/1.1 401 Unauthorized
S: WWW-Authenticate: Negotiate
The client will obtain the user credentials using the SPNEGO GSSAPI
mechanism type to identify generate a GSSAPI message to be sent to
the server with a new request, including the following Authorization
header:
C: GET dir/index.html
C: Authorization: Negotiate a87421000492aa874209af8bc028
The server will decode the gssapi-data and pass this to the SPNEGO
GSSAPI mechanism in the gss_accept_security_context function. If the
context is not complete, the server will respond with a 401 status
code with a WWW-Authenticate header containing the gssapi-data.
S: HTTP/1.1 401 Unauthorized
S: WWW-Authenticate: Negotiate 749efa7b23409c20b92356
The client will decode the gssapi-data, pass this into
Gss_Init_security_context, and return the new gssapi-data to the
server.
So, I don't think its possible for you to intermingle while the negotiation takes place as the process is internal

AngularJS / Restangular sessions

Do I need to do anything special concerning sessions/authentication? My server can't see my session after I login via restangular post method. The login is successful, but no sessions are being seen on the server afterwards. If I login with a normal post outside and then try to request a page, it works fine. I also notice that chrome is sending along the following in the headers:
Cookie:_rexbro_session=g2wAAAABaAJkAAxjdXJyZW50X3VzZXJhAmo=--E0687A67DBE48DA40B6957CBCF6E83FB0E612660
This is not being sent via restangular requests.
I should also mention that when I login via restangular, the server is responding with the following header:
set-cookie:_rexbro_session=g2wAAAACaAJkAAxjdXJyZW50X3VzZXJhAmgCZAAEdXNlcmgIZAASRWxpeGlyLlVzZXIuRW50aXR5ZAALRWxpeGlyLlVzZXJhAm0AAAASYXZvaWRpYW1AZ21haWwuY29tbQAAAA1KYXlzb24gQmFpbGV5bQAAADwkMmEkMTAkNHNsQVFFRGRDcXd1R3JNNVYwOXBTLkx3MHpVeElNLy5tbVM2eTVmc3BFa3NUbHhuTUtiMzJoB2QAFEVsaXhpci5FY3RvLkRhdGVUaW1lYgAAB91hBGEcYRFhD2EUaAdkABRFbGl4aXIuRWN0by5EYXRlVGltZWIAAAfdYQVhA2EVYR5hFmo=--D3F8D9A8355421B8571DB691ED5C082AD183B034; path=/; HttpOnly
but I don't see anything being done with it.
Thanks in advance.
EDIT: I should have stated that my server storing the session, the api server, is separate from the server serving the frontend.
I think your are looking for this:
RestangularProvider.setDefaultHttpFields({
withCredentials: true
});
add this lines to app.config

Resources