Keycloak - Getting 401 in Ajax calls after token expired - ajax

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.

Related

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

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 :)

Postman request receiving cookies but Postman not detecting them

I've recently started using Postman and I am testing an API where I get a CSRF token and then login but I always get a CSRF token mismatch. I am including X-XSRF-TOKEN header but I think the issue is around the cookies not being stored correctly.
I am calling a Laravel Sanctum endpoint to get a CSRF token and when I look in the in the console I can see Set-Cookie response headers
However, when I look in the cookies tab it says no cookies were received from the server
However, when I look at the cookies store they are listed for my test domain (home.local)
Due to this issue, when I send a request to the login, the session cookies are not sent in the request as shown in the console on the request to the login endpoint
I can do this fine using Insomnia.Rest client so I know the API is working as expected - I am however trying to replace Insomnia with Postman.
I've tried Google, but I've only found some bugs that were introduced that seemed to cause something similar back in 2016
Update
I managed to Postman working with production fine using a pre-request script to get the CSRF token and set the environment variable using the below:
const url = pm.environment.get('base_url');
const referer = pm.environment.get('Referer');
pm.sendRequest({
url: `${url}/sanctum/csrf-cookie`,
method: 'GET',
}, function (err, response, {cookies}) {
if (!err) {
console.log("cookies", cookies);
pm.environment.set('xsrf_token', cookies.get('XSRF-TOKEN'))
}
});
Although this worked on production and successfully did the POST request, on my local dev PC, I was still getting the CSRF mismatch.
Although the request/response looked the same between dev/and prod I for some reason had the idea to change my dev URL from my-app.home.local to my-app.home.com and now the cookies are received and send in the next request to login without getting a CSRF token mismatch.
There's clearly an issue with postman here but not sure if it's something I'm doing or a bug in Postman. Does .local mean something different?

Laravel Sanctum CSRF Token Mismatch using Thunder Cilent (Testing API)

CSRF Token Mismatch when using Thunder Cilent to test Laravel Sanctum API. Works fine on app, just not when testing API outside app.
Trying to test API with Laravel Sanctum with Thunder Cilent (Think Postman but VSCode extension). However, despite setting the xsrf-token and Cookie, I'm always getting CSRF token mismatch.
Note logging on works fine in the app, it's only the api testing with thundercilent that's failing.
Getting the CSRF Token
GET http://localhost:8000/sanctum/csrf-cookie
Response Cookies
xsrf-token: eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0%3D
laravel_session: eyJpdiI6IjV6VTV4di9IMXNST1ZvNVh0K1pZelE9PSIsInZhbHVlIjoiTSsycEVWdjJ1VTc4dU81TVNJWTJ4aTRHOE81WTVHVW1OeU55OEt3cVU3bHc5N090dEdPQy9yZGJsamhOaDUzaFZmZVp0Z2FTeGp4UWJyVFVmSDdnVytTNS9SZTF5c0daak9EZ1I1V0w3aWpjTnVESWtIRmR2QzNGZ1VqWlZHZ2oiLCJtYWMiOiI0Mzg4NGI4MTc5MGQ1MDE1NTUxY2VmNGRmNGFkNjUyYmI1MjUwMTJiODQ4NmY4M2E5OTRlZGRlNTM3NjAzNTg1IiwidGFnIjoiIn0%3D
Logging in
POST http://localhost:8000/login
Body: {
"email": "example#example.com",
"password": "password"
}
Raw Headers:
User-Agent: Thunder Client (https://www.thunderclient.com)
Accept: application/json
Referer: http://localhost:3000
xsrf-token: eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0%3D
Cookie: XSRF-TOKEN=eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0;laravel_session=eyJpdiI6IjV6VTV4di9IMXNST1ZvNVh0K1pZelE9PSIsInZhbHVlIjoiTSsycEVWdjJ1VTc4dU81TVNJWTJ4aTRHOE81WTVHVW1OeU55OEt3cVU3bHc5N090dEdPQy9yZGJsamhOaDUzaFZmZVp0Z2FTeGp4UWJyVFVmSDdnVytTNS9SZTF5c0daak9EZ1I1V0w3aWpjTnVESWtIRmR2QzNGZ1VqWlZHZ2oiLCJtYWMiOiI0Mzg4NGI4MTc5MGQ1MDE1NTUxY2VmNGRmNGFkNjUyYmI1MjUwMTJiODQ4NmY4M2E5OTRlZGRlNTM3NjAzNTg1IiwidGFnIjoiIn0%3D;
I copied how this SO Postman example, but it's not working at all. Thunder Cilent doesn't have pre-run scripts so I can't add cookies that way.
Open thunder client, switch to Env and create an environment.
Click on the options button of your collection and choose settings.
Add a test to your collection by navigating to the Tests tab, then select Set Env Variable, set query to cookie.xsrf-token and value to {{XSRF-TOKEN}}
In the Headers tab, add an header named X-XSRF-TOKEN with value {{XSRF-TOKEN | urlDecode}}.
Create a request to /sanctum/csrf-cookie to refresh CSRF Token if it expires
This should fix CSRF Token.
Consult docs for more information: https://github.com/rangav/thunder-client-support
Decided just to use token based auth for API testing instead. Much easier then messing around with CSRF cookies.
Notes for my future self.
When testing the Token based auth, you need to set the accept to application/json otherwise it won't work.
If using collections, don't delete the Accept header in the request, just untick the box. Otherwise the request would override the collection header with something else and not work.

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
}

Clicking an href to our Github oAuth endpoint works, but an AJAX request to same endpoint gives a CORS error?

We are trying to implement Github oAuth in our app using Passport.js. If the user hits the endpoint by clicking an anchor tag/href, it works fine, but if we use a click handler to initiate an ajax request instead, we receive a CORS error from the Github server. Why?
Server side code:
app.get('/auth/github',
passport.authenticate('github', { scope: [ 'user:email' ] }));
app.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
function(req, res) {
console.log('Github authentication successful!');
res.redirect('/');
});
Client side code (we are using React):
--> Works:
<a href='/auth/github'>Contact</a>;
--> Does Not Work - CORS error:
handleContactAuth(event) {
$.ajax({
url: '/auth/github',
method: 'GET',
success: data => console.log( 'Contact Auth response: ', data),
error: err => console.log( 'Error connecting to GitHub.', err)
});
}
NOTE: This is a click handler on the React component and is functioning fine, as the ajax request is being triggered. I'm aware we're not handling the response currently, apart from just a console.log.
--> CORS Error we see on the Client side when using AJAX method instead of href:
XMLHttpRequest cannot load https://github.com/login/oauth/authorize? response_type=code&redirect_uri=ht…auth%2Fgithub%2Fcallback&scope=user%3Aemail&client_id=our_client_code. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
Any ideas? Would appreciate any insights - many thanks.
CORS error is not an error returned by the server, but one triggered by the browser if the response doesn't contain HTTP headers signaling that cross-origin requests are allowed. The endpoint you are hitting obviously isn't designed to be accessed like this.
I had the same problem and I found that at least in Google Chrome XMLHttpRequest object is restricted to same origin policy. So you may need to stick to using an anchor tag.
Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they're limited by the same origin policy. Extensions aren't so limited. An extension can talk to remote servers outside of its origin, as long as it first requests cross-origin permissions.
Link: https://developer.chrome.com/extensions/xhr

Resources