shiro pac4j cas ajax 401 when accessing another client - single-sign-on

I am using cas 5.x.
I have cas-server and two web apps client-1 and client-2.
currently, I can single sign on and single sign out, but there is one problem in following steps:
access client-1, it will ask me for login in cas server, then redirect me back to client-1 after login success.
click one button to access the protected resources of client-2 via ajax in page of client-1, however this ajax call return 401.
if i access protected resources of client-2 from browser address bar directly in step 2, it works.
ajax cannot handle the redirect cause this problem, thus how to solve this problem?
my ajax call is :
//test() is in client-1
function test() {
jQuery.ajax({
url:"http://192.168.0.14:8445/client-2/user/userInfo",
headers: {'X-Requested-With': 'XMLHttpRequest'},
success: function(res) {
//...
}
});
}

Per the pac4j documentation,
When you're using an Indirect Client, if the user tries to access a protected URL, the request will be redirected to the identity provider for login. Though, if the incoming HTTP request is an AJAX one, no redirection will be performed and a 401 error page will be returned.
So what you're seeing is expected behavior.
Next, the HTTP request is considered to be an AJAX one if the value of the X-Requested-With header is XMLHttpRequest or if the is_ajax_request parameter or header is true. This is the default behavior/condition when handling/detecting AJAX requests, and by default, pac4j will only compute the redirection URL and add it as a header (assuming the addRedirectionUrlAsHeader is set to true for the indirect client) when it passes back the 401 http status.
ajax cannot handle the redirect cause this problem
It's not designed to handle the redirects. You need to catch the 401 in your AJAX call, take the redirect url from the header that is passed back to you and do the redirect yourself automatically, or do any other activity/action that is correct behavior for your application (display message, redirect to another URL, etc).

Related

How to read server set cookie in angular 2 application, and how to send same cookie in request from angular 2 application?

Requirement : Our application needs to support same user opening our web application as separated session.
The problem is not how to use cookies in angular 2, but how can sever get cookie from HTTPServletRequest object when angular 2 application makes a rest call to server.
Implementation: Server side restful application has one filter to set user's browser session in cookie and then in HttpServletResponse. Angular client is making one call upon application bootstrap, which is going through server filter to set user's browser session in cookie.
Problem statement: angular client is making first rest call which goes through server filter to set the browser session cookie. When i open chrome developer tool, i do see that rest api response has "set-cookie" which has cookie set by server, but when i open the application tag in developer tool, i do not see any cookie set.
After that if I make any other rest call through angular application, it does not send the cookie in either request or request headers. Now, our application rest api depends on this cookie value to be present in HttpServletRequest and now it is failing.
Can someone please guide me here? I must have done something wrong on angular 2 application side, which i am not able to catch.
I have tried passing "withCredentials =true", but no change.
Another thing I noticed, if i make "GET" request, then i do see cookie in request header, but for "POST" request, I do not see anything for cookie.
Please advice.
server side code to set cookie
String uniqueId = RandomStringUtils.randomAlphanumeric(32);
Cookie userSessionCookie = new Cookie("userSessionId", uniqueId);
if (getDefaultDomain() != null) {
userSessionCookie.setDomain(getDefaultDomain());
}
httpServletResponse.addCookie(userSessionCookie); httpServletResponse.addHeader("Access-Control-Allow-Credenti‌​als", "true"); httpServletResponse.addHeader("access-control-allow-methods"‌​, "GET, POST, PUT, PATCH, DELETE, OPTIONS");
httpServletResponse.addHeader("Access-Control-Allow-Headers"‌​, "Content-Type, token,withCredentials");
angular 2 post request which expects server to get cookie from HttpServletRequest
renderFF() {
//prepare renderFInput object
var fcRenderInput = {};
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers, withCredentials: true
});
this._http.post('/api/v1/render/feature/fc',fcRenderI‌​nput,options)
.subscribe((res) => {
console.log(res.json());
});
}
Just a suggestion if this is about only one browser and multiple tabs, in this case you can use the local storage while setting some flag in it. Also when you try to open the same application in the new tab. you check if the flag is there and user is trying to open the same web application in some other tab of the same browser. You also need to delete the local storage you had set after some point.
I hope if you can get some trick to solve this issue :)

Get location fragment with Fetch API redirect response

I am trying to get the redirect response location fragment of a fetch API request. But I can't figure how to access it, if possible.
The context is that I am doing an OpenID Connect request in implicit flow, for a WebRTC Identity Proxy assertion generation.
OIDC specs define the answer of the request as:
When using the Implicit Flow, all response parameters are added to the
fragment component of the Redirection URI
HTTP/1.1 302 Found
Location: https://client.example.org/cb#
access_token=SlAV32hkKG
...
So I'm making the request with fetch set in manual mode. But the response is then an opaque-redirect filtered response, which hides the location header. (https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect)
Other mode for fetch are error and follow which would not help. While XHR automatically follows the redirect so would not help either. I may be missing something from the fetch API, but it seems to be something hidden on purpose.
Could someone gives me a way to access this information (or a confirmation it's impossible) ?
Is there any alternative to fetch and XHR to make this request, which would allow to access the redirect location header?
Since XHR automatically / opaquely follows redirects (in the event you're using the whatwg-fetch polyfill for example), one possible solution is to check the response.url of the fetch resolution, to see if it matches a redirect location that you expect.
This only helps if the possible redirect locations are limited or match some pattern --- for instance, if you could expect at any time to be redirect to /login:
function fetchMiddleware(response) {
const a = document.createElement('a');
a.href = response.url;
if (a.pathname === '/login') {
// ...
} else {
return response;
}
}
fetch(`/api`)
.then(fetchMiddleware)
.then(function (response) {
// ...
});
fetch isn't able to polyfill the entire standard. Some notable differences include:
Inability to set the redirect mode.
See David Graham comment on the Disable follow redirect:
This is a nice addition to the Fetch API, but we won't be able to polyfill it with XMLHttpRequest. The browser navigates all redirects before returning a result, so there is no opportunity to interrupt the redirect flow.
My Solution:
1). First solution: we are sending 200 status and redirect url(in the http header) from the server and client is redirecting based on that.
2). Second solution: Server could also redirect to with 301 and redirect url. I think, This is the best solution(i.e if we consider SEO).

How to capture redirect response header

I am trying to record a simple login and logout flow for a .Net application. After I submit the login credentials the welcome page's URL has a large alpha numeric number. This number is required to continue to the next steps.
On Fiddler I have noticed that the login credential submission request results in a 302 response and this response contain an a=129characterstring that i need in my subsequent requests.
On JMeter I have added a recording controller and on the HTTP(S) Test Script Recorder I have Follow Redirects and Use KeepAlive checked (See below screenshot)
I have also recorded with Follow Redirects unchecked and different options for Grouping and HTTP Sampler Settings.
But with none of them I am able to record/capture the 302 response that i see on fiddler. Instead the login credential submission request always returns a 200 response, even if the login fails.
It is not as if that JMeter is not recording redirect requests, further down the scenario flow I have another redirect request which is captured.
I can't be the only one who is/has faced this problem. Does anyone have any suggestions on what I should be doing differently to get the 302 response?
To do this:
Record with default options, the redirect Http Request triggered by 302 will be disabled by default.
Then you will need after this to uncheck "Follow Redirect" in the first one, and add a Regular Expression Post Processor to extract the data you want.
Then enable the commented second request and inject the extracted variable.

Handling HTTP 302 error and redirecting in Backbone.JS "sync" method

I've got a secured Backbone.js app (that uses Spring security atm.), so a logged-in user must have a valid session-cookie (JSESSIONID). Now, if this session is invalidated (deleted, expired, whatever) and the user attempts to make a request, Spring security will return a 302 Error as an attempt to redirect the user to a login-form.
As is explained in this answer, this 302 response gets handled by the browser (it doesn't reach my app) so what is returned to my app is a 200 OK response with contenttype="text/html" (containing the login form).
Thats an issue, because when my Backbone model attempts to do a sync to a url, it expects JSON. If this sync happens without a valid session, the 200 "text/html" response is returned when "application/json" is expected, giving me a JSON parse error in jQuery.extend.parseJSON.
With great help from this question/answer, I've overridden the Backbone.sync method in order to use my own error handling. However, since the 302 never reaches my error handler I cannot override the redirect myself.
My situation is very similar to this question, however a final solution to the problem was never posted. Could someone please help me figure out the ideal way to ensure a redirect to the login page happens?
Instead of returning the login page with HTTP 200 OK, you should configure Spring Security to return HTTP 401 Unauthorized for unauthenticated AJAX requests. You can detect an AJAX request (as opposed to a normal page request) by checking for the X-Requested-With: XMLHttpRequest request header.
You can use the global $.ajaxError handler to check for 401 errors and redirect to the login page there.
This is how we've implemented it and it works nicely. I'm not a Spring guy, though, so I can't really help with the Spring Security configuration.
EDIT. Instead of custom coockie it will be better to use solution provided by #fencliff.
I think you can use some other field of XHR to detect this situation. A special coockie may do the trick.
You can define your own authentication failure handler from Spring Security side. At the moment when redirect to login page occurs you will be able to add some coockie to HttpServletResponse. Your custom Backbone.sync method will check this cookie. If it is present, it will launch your custom handler for this case (do not forget remove the coockie at the same time).
<sec:http ... >
<sec:form-login login-page='/login.html' authentication-failure-handler-ref="customAuthenticationFailureHandler" />
</sec:http>
<bean id="customAuthenticationFailureHandler" class="com.domain.CustomAuthenticationFailureHandler" />
CustomAuthenticationFailureHandler must implement org.springframework.security.web.authentication.AuthenticationFailureHandler interface. You can add your coockie and then call default SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure(...) implementation.

How to handle redirect 302 in extjs form submit

Before form is submit it go through session checking and if session was timeout it will redirect to login page. This is where the problem occur because it is ajax form submit so failure: handle this, but what extjs return to separate redirect from other kind of error ?
If your session is timed out, do you need the server to return a json and handle it in the failure or success handler of the ajax request?
But if your session has expired, you need to redirect the user to your login page. In that case, you need not worry about what the json response would be and how ExtJS will handle it.
For a 302 error, its the web server that provides the alternate URL and not the browser. The 302 response from the Web server should always include an alternative URL to which redirection should occur. So, in your case you should redirect the user to the login page of your application.