Keycloak: CORS issue after (JSF) AJAX call when the session has expired - ajax

I'm having CORS issues when using Keycloak (20.0.1) as my authentication mechanism with WildFly 26.
Everything works fine as long as the Keycloak session is active.
However, when the session expires and I perform an AJAX call, I get an HTTP 302 response redirecting me to the keycloak login page and a subsequent CORS issue with "No 'Access-Control-Allow-Origin' header is present on the requested resource". The end user result is that the AJAX call doesn't do anything.
``
As far as I understand I should rather get an HTTP 401 and handle it in my front to perform a full browser redirect (as suggested here: https://keycloak.discourse.group/t/cors-error-in-refresh-token-by-xhr/7818). The problem is that none of my code gets executed before this redirect (I tried to add a CorsFilter for instance, adding the needed headers). I can see that some undertow code gets executed, but then nothing below that.
So I don't see how I could change this behavior and provide a 401 instead of 302 in this case (I'm using JSF and the line between front/back is thin), which is why I write this message.
I've configured a oidc.json file in my app (and therefore configured OIDC as auth method in the web.xml):
"realm": "myRealm",
"auth-server-url": "http://x.x.x.x:8080/",
"ssl-required": "none",
"resource": "myClient",
"verify-token-audience": true,
"credentials": {
"secret": "someSecret"
},
"use-resource-role-mappings": false,
"enable-cors": true,
"cors-allowed-methods" : "*",
"cors-exposed-headers" : "*",
"cors-allowed-headers" : "*",
"cors-max-age" : 123456789,
I've tried adding filters in the Wildfly standalone.xml, under the undertow subsystem:
<response-header name="Access-Control-Allow-Origin" header-name="Access-Control-Allow-Origin" header-value="*"/>
<response-header name="Access-Control-Allow-Methods" header-name="Access-Control-Allow-Methods" header-value="*"/>
<response-header name="Access-Control-Allow-Headers" header-name="Access-Control-Allow-Headers" header-value="*"/>
<response-header name="Access-Control-Allow-Credentials" header-name="Access-Control-Allow-Credentials" header-value="true"/>
And I've set Web Origins to "*" in Keycloak (also: confidential mode)
As for my environment, I'm using a JSF application (packaged in a WAR, and this war is packaged in an EAR alongside other custom JAR/libs)
Any help on this would be greatly appreciated. If I can provide more details, don't hesitate to ask :)

Related

Spring Security + Keycloak - setting no bearer token to REST request leads to an response with HTML content

I'm using a SpringBoot 2 (2.7.0) application (including Spring security 5.7.1) to secure REST endpoints with Keycloak for authentication and authorization. Everything works fine but the only thing which bothers me is when I don't set the bearer token I get a HTTP 400 response. The response itself is correct but the body of the response contains HTML (Keycloak login page).
Is there a way to avoid that the body of the response contains the login page? I would like to set a custom response body.
That is an expected default behavior. If you want to instead get relevant 4xx error instead, you can try setting the the "bearer-only" in your "keycloak.json" file so that it would not redirect API calls (i.e. AJAX calls from browser) to the login page:
{
...
"bearer-only": true
}

IdentityServer4 - "path was not for an allowed IdentityServer CORS endpoint"

I have a .netcore 3.1 MVC project that is using IdentityServer4 hybrid flow.
The identityServer has an AuthenticationProperties with ExpiresUtcset (1 hour).
On the MVC client, there's a cookie with SlidingExpiration (false) and ExpireTimeSpan (15 minutest).
After logging in to the application, the application cookie shows that it has an expiry of 1 hour. If I refresh or change to another page within the application, the application cookie gets the expiry extended. So far, this are all working as expected.
However, things get hairy if I try to do ajax calls on a page. If I stay on a page for a while (more than 15 minutes) without refreshing, the ajax calls fail with a Http error 0. When looking through the IdentityServer logs, it shows:
CORS request made for path: /connect/authorize from origin:
http://localhost:5100 but was ignored because path was not for an
allowed IdentityServer CORS endpoint
No CORS policy found for the specified request.
To deal with that, the following was added in the startup.cs ConfigureServices for the identityServer.
var cors = new DefaultCorsPolicyService(log)
{
AllowAll = true
};
services.AddSingleton<ICorsPolicyService>(cors);
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins("http://localhost:5100")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
In configure, app.UseCors() was added.
However the same error messages in IdentityServer still comes up.
Has anyone faced similar issues, or know what I'm doing wrongly?
You also may need to add CORS in your client settings. See this. You have to add your URL in AllowedCorsOrigins settings for your client.
Calling the app.UseCors() before app.UseEndpoints() in Configure method will solve this issue.

Keycloak - Getting 401 in Ajax calls after token expired

I'm using keycloak 3.4.3 Server and 3.4.3 springboot adapter. The login is done with the Java Adapter using the following json configurations:
{
"realm": "real name",
"auth-server-url": "http://172.21.34.65/auth",
"ssl-required": "external",
"resource": "appName",
"public-client": true,
"use-resource-role-mappings": true
}
The token is refreshed with non ajax calls but when the request contains the header X-Requested-With: XMLHttpRequest the token is not refreshed. Is there a problem with my config or is this the normal behavior and I need to use the JS adapter to refresh the token before the ajax calls?
For me an ugly solution was to embed an invisible self-reloading iframe that causes the token refresh. But that is the source of another problem: sometimes the redirect after logging into keycloak leads to the iframe url rather than the desired one.
I haven't found a satisfying solution yet.

How to make a cross domain request and send cookies with the request

I'm developing 2 applications for backend and frontend (spring mvc and angular4), i host the spring app on tomcat server using the http port (8080) and my frontend using the http port (4200) , the communication between both of the apps is made using json.
To identify the user i'm using a session cookie.
The problem is that i came to a cross domain issue because i use different ports for both of the apps, the cookie is not send when i make a http post request.
the only solution i found until now :
When i put the angular app inside the /src/main/webapp of my spring project, following this documentation , i dont have the issue and the cookie are automatically set but it's painful to do the previous steps everytime when i want to test something.
I thought also about some workaround like jsonp during the development process but i don't think this would be productive plus later on i need to execute some e2e testing.
Did anyone have an idea/example about how to make this cross domain...
Communication can be established via proxy.
Add in package.json "start": "ng serve --proxy-config proxy.config.json" in "scripts".
In proxy.config.json add:
{
"/api/*": {
"target": "http://server.com:8080",
"secure": false,
"logLevel": "debug"
}
}
So after some researches i came out with this solution :
in term of security and scalability of my backend service the best way is to use CROS, any other solutions like JSONP, Proxy is just a workaround and this will bring nothing because of the same-origin-policy followed by browsers
Following this documentation the scenario that will happens :
1) the browser will send an OPTIONAL request
2) the server will response with the allowed origin
3) if the our domain(the frontend domain/ angular app in my case) is verified the cookies will be automatically send in another request
the implementation that i made :
in the spring app :
//Some logic before this
if ("OPTIONS".equals(request.getMethod())) {
response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Methods","POST");
response.setHeader("Access-Control-Allow-Headers","Content-Type");
response.setStatus(HttpServletResponse.SC_OK);
((HttpServletResponse) servletResponse).setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
return ;
}
// some logic after this
in the front app we can send the request using xhr like it's described in the attached link.

Old Session and cookies are referred while processing new HttpServletRequest in Spring

My Restful service application developed with Spring 3 is deployed on Tomcat7. I use a chrome app called Postman to request REST services.
I have a custom-filter placed before all the services (except the non-secured service login) for checking Session and application specific cookies.
This filter returns HTTP status:403 if cookies are not present or invalid in the incoming HttpServletRequest. The filter (myCustomSecurityFilter) is Singleton scoped.
<security:intercept-url pattern="/services/v1/login" access="permitAll()" /> <security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myCustomSecurityFilter" />
I observed that in a speicific case old HttpServletRequest is used. Please see the following scenario
Case #1: All requests are over HTTPS.
All cookies are removed and browser is restarted.
Call HTTPS serveice: ..\services\v1\account. As this service has to be called after ..\services\v1\login, 403 response is expected. Actual response :403.
A new session is created and a JSESSIONID is returned by Tomcat. Say, JSESSIONID_1.
Request ..\services\v1\login . Expected and Actual respose 200-OK.
A new session is created and a JSESSIONID is returned by Tomcat. Say, JSESSIONID_2.
Also application specific cookies are returned to client/browser. Say, App_cookie_1.
Call any other service other than ..\services\v1\account. All are successful(200).
Cookies JSESSIONID_2, App_cookie_1 are sent by request and the same can be observed after receiveing response.
Note: This step is not important. Step 5 can be reproduced with or without this step.
Request ..\services\v1\account. Expected: 200. Actual: 403.
When I debugged the HttpServletRequest received by the doFilter method in my SecurityFilter mentioned above, I see that the HttpServletRequest contains old cookies of the previous request to the same service ..\services\v1\account.
i.e JSESSIONID_1. No app cookie.
I checked cookies with the help of httpServletRequest.getCookies()
Browser shows Cookies JSESSIONID_2, App_cookie_1. I am sure that Tomcat receieves Cookies JSESSIONID_2, App_cookie_1 becasue I have access logging enabled.
So, it is Spring (my code or config on top of the the Spring framework) passing old HttpServletRequest to filter. But how? why?
Request ..\services\v1\account. Expected: 200. Actual: 200.
Cookies JSESSIONID_2, App_cookie_1 are sent by request and the same can be observed after receiveing response.
So, old cookies are used only once and that too only for the same service in this case (..\services\v1\account).
Webapp works as expected in the following cases (above steps are denoted by numbers):
Case2: 1,2,3,4,5,6. All are HTTPS requests except #2 (login request). #5 returns 200 if #2 is a HTTP request!! This is also a surprise.
Case3: 2,3,4,5,6.
Case4: 2,3,5,6.
Step 4 is not important. Step 5 can be reproduced with or without this step. I placed this to explain that previous request of the same service is loaded and no effect on other services.
Problem is definitely not with ..\services\v1\account. Because I just used this service for example. Case 1 can be repeated with any secured service in the application.
I tried managing my session with newSessio, migrateSession options.
<security:session-management session-fixation-protection="newSession">
<security:concurrency-control max-sessions="1" />
</security:session-management>
Thanks for reading my question. It would be great if you could give some pointers.
Please let me know if I am not clear.
(answering as I faced a similar problem recently. Even though the question is too old)
..\services\v1\login
A new session is created and a JSESSIONID is returned by Tomcat.
How is the session created here?
Assuming its typically created like below
HttpServletRequest.getSession(true);
there is a high chance server gets an old session here.
Can you try a
session.Invalidate()
or
request.logout()
So that any open session is invalidated.after that you try creating the session again
HttpServletRequest.getSession(true);

Resources