UI5 - Routing with http header parameter - spring-security

I have developed a small UI5 application with OData calls and I am implementing the authorizations. On the server I have a spring web security application where have I have restricted the MemebersList.view.xml to users that are logged in. My idea is to create under the folders view and controller a folder user for the logged in users with the files that are restricted.
After the user is successfully logged in I receive the token to make further OData calls.
Question 1:
Can I restrict in UI5 the resources(XML views and JS controllers) so that the user only has direct access when is logged in or the sources have to be visible allways? For example I want to avoid that the user can directly enter
http://localhost:9004/webapp/view/user/MembersList.view.xml or
http://localhost:9004/webapp/controller/user/MembersList.controllers.js and see the code
Question 2:
Can I somehow send the Token in the header when I call the view MembersList with the getRouter().navTo('MembersList')? I guess in this case I would also need somehow to pass the token to the call for the MembersList.controllers.js. Maybe set that all the http calls include the token?
Maybe there is a better approach
WebSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**/user/**").hasRole("USER")
.antMatchers("/**/admin/**").hasRole("ADMIN")
.antMatchers("/",
"/favicon.ico",
"/**/*.json",
"/**/*.xml",
"/**/*.properties",
"/**/*.woff2",
"/**/*.woff",
"/**/*.ttf",
"/**/*.ttc",
"/**/*.ico",
"/**/*.bmp",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.jpeg",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/**/*.html").permitAll()
.antMatchers("/**/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
onClickEvent:
onLoginTap : function() {
var navigator_info = window.navigator;
var screen_info = window.screen;
var uid = navigator_info.mimeTypes.length;
uid += navigator_info.userAgent.replace(/\D+/g, '');
uid += navigator_info.plugins.length;
uid += screen_info.height || '';
uid += screen_info.width || '';
uid += screen_info.pixelDepth || '';
this.loginBody.getData().email = this.login.getData().user;
this.loginBody.getData().password = this.login.getData().password;
this.loginBody.getData().deviceId = uid;
// create XHR object
var xhttp = new XMLHttpRequest();
var that = this;
// gets everytime fired when the XHR request state changes
xhttp.onreadystatechange = function() {
// 4 means request is finished and response is ready
// 200 means request is just fine
if (this.readyState == 4 && this.status == 200) {
// "this" refers here to the XHR object
var json = JSON.parse(this.responseText)
if (json.accessToken !== null) {
that.login.getData().accessToken = json.accessToken;
that.getRouter().navTo("MembersList");
}
}
};
// set the XHR request parameters
xhttp.open("POST", "http://localhost:9004/api/auth/login", true);
xhttp.setRequestHeader("Content-Type", "application/json");
// fire the XHR request
xhttp.send(JSON.stringify(this.loginBody.getData()));
}

Related

OpenIdConnect Correlation Cookie not found when user click link from Office application

I have an app that is authenticating with Azure Active Directory using OpenIdConnect. Everything is working fine except when I link to my site from a Office Application (excel/ word). From these applications I get a "Exception: Correlation failed.".
From my research it seems to be that office is first doing the 302 redirect and then opening that page not the original link.
See: https://github.com/aspnet/Security/issues/1252
After a recommendation for how to handle this scenario. I don't want to have to make to many changes to the authentication flow and introduce bugs.
I have tried redirecting to a different page on my site when a user-agent of excel is detected. I thought then the correct cookie would be set and I could redirect from there to the requested page which would then trigger authorization. No luck though
OnRedirectToIdentityProvider = context =>
{
if (context.Request.Headers["User-Agent"].ToString().Contains("Microsoft Office Excel"))
{
string redirect = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + "/Home/Office" + "?url=" + context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + context.Request.Path;
context.Response.Clear();
context.Response.Redirect(redirect);
context.HandleResponse();
return Task.CompletedTask;
}
}
I was able to implement a decent solution using owin middleware. Largely with the help of this post: https://github.com/aspnet/AspNetKatana/issues/78
I how ever did need to convert it to .net core 2.0. Here's the converted code:
public class MsOfficeLinkPrefetchMiddleware
{
RequestDelegate _next;
public MsOfficeLinkPrefetchMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
if (Is(context, HttpMethod.Get, HttpMethod.Head) && IsMsOffice(context))
{
// Mitigate by preempting auth challenges to MS Office apps' preflight requests and
// let the real browser start at the original URL and handle all redirects and cookies.
// Success response indicates to Office that the link is OK.
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
context.Response.Headers["Pragma"] = "no-cache";
context.Response.Headers["Expires"] = "0";
}
else if (_next != null)
{
return _next.Invoke(context);
}
return Task.CompletedTask;
}
private static bool Is(HttpContext context, params HttpMethod[] methods)
{
var requestMethod = context.Request.Method;
return methods.Any(method => StringComparer.OrdinalIgnoreCase.Equals(requestMethod, method.Method));
}
private static readonly Regex _msOfficeUserAgent = new Regex(
#"(^Microsoft Office\b)|([\(;]\s*ms-office\s*[;\)])",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
private static bool IsMsOffice(HttpContext context)
{
var headers = context.Request.Headers;
var userAgent = headers["User-Agent"];
return _msOfficeUserAgent.IsMatch(userAgent)
|| !string.IsNullOrWhiteSpace(headers["X-Office-Major-Version"]);
}
}
Startup
app.UseMiddleware<MsOfficeLinkPrefetchMiddleware>();
Hope this is able to help someone in the future.
In Startup.ConfigureServices(), you'll need to add this line
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for
//non -essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

Spring security and Stomp not seeing headers

I have a spring security application and I'm trying implement websockets using Stomp.
The application is mainly REST based, using tokens for security. All requests coming in have to a security token in the header.
The problem is when setting up a simple Stomp client using basic html, spring appears to not be seeing any headers.
The client works fine if I disable the security, in which case no headers are passed in.
var socket = new SockJS('http://localhost:8080/project/ws/wsendpoint');
var headers = {'Auth': 'some_auth_token'}
writeConsole("Created socket");
stompClient = Stomp.over(socket);
stompClient.connect(headers, function(frame) {
writeConsole("Connected to via WebSocket");
stompClient.subscribe('/topic/push', function(message)
{ writeConsole(message.body);}, headers );
});
window.onbeforeunload = disconnectClient;
Heres the relevant spring configuration
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers().frameOptions().sameOrigin()
.and()
.authorizeRequests()
.anyRequest().authenticated() authenticated.
.and()
.anonymous().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());
http.addFilterBefore(authenticationTokenFilter(), BasicAuthenticationFilter.class);
}
The doFilter in the authenticationTokenFilter class should see the header field 'Auth', as set in the client, however nothing is there.
Instead of sending header, you can replace sessionId with your own ID.
var sessionId = utils.random_string(36);
var socket = new SockJS('/socket', [], {
sessionId: () => {
return sessionId
}
});
stompClient = Stomp.over(socket);
stompClient.connect(headers, function(frame) {
writeConsole("Connected to via WebSocket");
stompClient.subscribe('/topic/push', function(message)
{ writeConsole(message.body);}, headers );
});
Stomp cannot send any custom headers during the initial authentication stage. The way round this was to send the authentication token as a query parameter (would not recommended for non-closed systems).

Setting the redirect_uri in Asp.Net Identity

I am trying to set the redirect_uri for the facebook login with Asp.Net Identity. However, the GetExternalLogin REST method in the AccountController is only triggered if the redirect_uri is '/'. If I add anything else it does not trigger GetExternalLogin, the browser only shows error: invalid_request.
However the url contains the redirected parameter as it should e.g. if I add the redirect_uri as http://localhost:25432/testing
the response URL looks like this:
http://localhost:25432/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A25432%2Ftesting&state=0NctHHGq_aiazEurHYbvJT8hDgl0GJ_GGSdFfq2z5SA1
and the browser window shows: error: invalid_request
Any idea why this works only when redirecting to '/' but not to any other urlĀ“s?
For anyone else that might run into this issue: the problem is when you take (copy) the ApplicationOAuthProvider.cs from the Visual Studio SPA template and it is there where this code is:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
var expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
This will obviously block any redirect_uri that doesn't look like http://localhost/ or http://example.com/ so for instance http://example.com/home won't work.
Now this below is the source for InvokeAuthorizeEndpointAsync in Katana which does all the work and you can see it calls into any custom OAuthProvider that might be registered for this MVC/Web API application (this registration typically happens in Startup.Auth.cs):
private async Task<bool> InvokeAuthorizeEndpointAsync()
{
var authorizeRequest = new AuthorizeEndpointRequest(Request.Query);
var clientContext = new OAuthValidateClientRedirectUriContext(
Context,
Options,
authorizeRequest.ClientId,
authorizeRequest.RedirectUri);
if (!String.IsNullOrEmpty(authorizeRequest.RedirectUri))
{
bool acceptableUri = true;
Uri validatingUri;
if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
{
// The redirection endpoint URI MUST be an absolute URI
// http://tools.ietf.org/html/rfc6749#section-3.1.2
acceptableUri = false;
}
else if (!String.IsNullOrEmpty(validatingUri.Fragment))
{
// The endpoint URI MUST NOT include a fragment component.
// http://tools.ietf.org/html/rfc6749#section-3.1.2
acceptableUri = false;
}
else if (!Options.AllowInsecureHttp &&
String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
{
// The redirection endpoint SHOULD require the use of TLS
// http://tools.ietf.org/html/rfc6749#section-3.1.2.1
acceptableUri = false;
}
if (!acceptableUri)
{
clientContext.SetError(Constants.Errors.InvalidRequest);
return await SendErrorRedirectAsync(clientContext, clientContext);
}
}
await Options.Provider.ValidateClientRedirectUri(clientContext);
if (!clientContext.IsValidated)
{
_logger.WriteVerbose("Unable to validate client information");
return await SendErrorRedirectAsync(clientContext, clientContext);
}
var validatingContext = new OAuthValidateAuthorizeRequestContext(
Context,
Options,
authorizeRequest,
clientContext);
if (string.IsNullOrEmpty(authorizeRequest.ResponseType))
{
_logger.WriteVerbose("Authorize endpoint request missing required response_type parameter");
validatingContext.SetError(Constants.Errors.InvalidRequest);
}
else if (!authorizeRequest.IsAuthorizationCodeGrantType &&
!authorizeRequest.IsImplicitGrantType)
{
_logger.WriteVerbose("Authorize endpoint request contains unsupported response_type parameter");
validatingContext.SetError(Constants.Errors.UnsupportedResponseType);
}
else
{
await Options.Provider.ValidateAuthorizeRequest(validatingContext);
}
if (!validatingContext.IsValidated)
{
// an invalid request is not processed further
return await SendErrorRedirectAsync(clientContext, validatingContext);
}
_clientContext = clientContext;
_authorizeEndpointRequest = authorizeRequest;
var authorizeEndpointContext = new OAuthAuthorizeEndpointContext(Context, Options);
await Options.Provider.AuthorizeEndpoint(authorizeEndpointContext);
return authorizeEndpointContext.IsRequestCompleted;
}
This is key:
await Options.Provider.ValidateClientRedirectUri(clientContext);
So your solution is to change how the ValidateClientRedirectUri performs the validation - the default SPA implementation is, as you can see, very naive.
There's lots of ppl having issues with SPA mainly because it lacks any kind of useful information and I mean that both for ASP.NET Identity and OWIN stuff and with regards to what is going on within KnockoutJS implementation.
I wish Microsoft would provide more comprehensive docs for these templates because anyone who will try to do anything a bit more complex will run into issues.
I've spent hours on this, digging into OWIN (Katana) source code thinking it is the above implementation that blocks my redirect URIs but it was not, hopefully helps someone else too.
HTH
The problem is that GetExternalLogin registered as OAuthOptions.AuthorizeEndpointPath which used for app.UseOAuthBearerTokens(OAuthOptions). This configuration puts validation on arguments of endpoint.
if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
{
// The redirection endpoint URI MUST be an absolute URI
}
else if (!String.IsNullOrEmpty(validatingUri.Fragment))
{
// The endpoint URI MUST NOT include a fragment component.
}
else if (!Options.AllowInsecureHttp &&
String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
{
// The redirection endpoint SHOULD require the use of TLS
}
And you should pass "Authorize endpoint request missing required response_type parameter" and
"Authorize endpoint request contains unsupported response_type parameter"
Based on the other answers, I changed the Validation code in ApplicationOAuthProvider.cs to just ensure that the redirect uri is on the same domain like so:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (context.RedirectUri.StartsWith(expectedRootUri.AbsoluteUri))
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}

how to secure or set a rails style before_filter for all angular controllers?

I'm using angularjs for the front end and rails + devise for authentication on the backend.
On the front end I have added a responseInterceptor to redirect to the /#/sign_in page upon any 401 response from any xhr request and display a growl style pop-up message using toastr.
App.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('securityInterceptor');
}]);
App.factory('securityInterceptor', ['$injector', '$location', '$cookieStore', function ($injector,$location,$cookieStore) {
return function(promise) {
var $http = $injector.get('$http');
return promise.then(null, function(response){
if (response.status === 401) {
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
});
};
});
My problem is, when I click on a page that loads several xhr requests during the controllers initialization, for example:
var products = Product.query();
var categories = Category.query();
var variations = Variation.query();
These are needed for various navigation components and they all fire off in parallel, resulting in several duplicate growl-style messages.
Is there a way to make angular quit on the first 401 and stop execution of the rest of the controller from within the interceptor? In a traditional rails app, there would be a "before_filter" that stops regular execution, preventing the page and queries from loading... what's the best way to do this in angular?
I've been pondering about this problem for my own apps too. A sketch of my thoughts (NOT REAL IMPLEMENTATION, SO BEWARE):
A userData service keeps track of whether the user is logged in + other information (e.g. user name, real user name etc):
App.service("userData", function() {
var currentData = {
loggedIn: false
};
function getCurrent() {
return currentData;
}
// called when the user logs in with the user data
function loggedIn(userData) {
// the object is REPLACED to avoid race conditions, see explanation below
currentData = angular.extend({loggedIn: true}, userData);
}
return {
getCurrent: getCurrent,
loggedIn: loggedIn
};
});
The interceptors keep track of the currentData. If an interceptor receives HTTP 401 and the loggedIn flag is true, it changes the flag to false and redirects to the login view. If an interceptor receives HTTP 401 and the loggedIn flag is false, it does nothing besides rejecting the request, because another interceptor has done the view redirection.
When the user logs in, the currentData is replaced, so as to avoid situations with delayed responses (e.g. call1 and call2 are initiated, call1 responds 401; call2 also results in 401, but the delivery of the actual response is delayed; then the user logs in again; then call2 receives its 401; the second 401 should not overwrite the current state)
App.config(["$provide", "$httpProvider", function($provide, $httpProvider) {
$provide.factory("myHttpInterceptor", ["$q", "userData", "$cookieStore", "toastr", "$location",
function($q, userData, $cookieStore, toastr, $location) {
return {
request: function(config) {
config.currentUserData = userData.getCurrent();
return config;
},
responseError: function(rejection) {
if( rejection && rejection.status === 401 && rejection.config && rejection.config.currentUserData && rejection.config.currentUserData.loggedIn ) {
rejection.config.currentUserData.loggedIn = false;
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
return $q.reject(rejection);
}
};
}
]);
$httpProvider.interceptors.push("myHttpInterceptor");
});
Also note I am using the newer way to register interceptors, as $httpProvider.responseInterceptors seems to be deprecated.

Retrieving oauth_token and oauth_token_secret (RestSharp on Windows Phone)

New to OAuth and RestSharp.
I'm building a Windows Phone app, using RestSharp to handle all the OAuth stuff.
private void LoginButton_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
RestClient client = new RestClient(OAuth.Auth.baseurl);
client.Authenticator = RestSharp.Authenticators.OAuth1Authenticator.ForRequestToken(OAuth.Auth.consumerkey, OAuth.Auth.consumersecret);
var request = new RestRequest("oauth/request_token", Method.POST);
client.ExecuteAsync(request, (response) =>
{
var resource = response.Content;
MessageBox.Show(resource);
webBrowser1.Navigate(new Uri(OAuth.Auth.UAuthorise + "?" + resource + "&oauth_callback=http://bing.com"));
});
}
private void webBrowser1_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
MessageBox.Show(e.Uri.ToString() + " loaded");
}
I'm successfully getting the request token, and then a user is able to log in to the service. webBrowser1 then navigates to bing.com (as defined in the callback url). My question is: what do I do to get the oauth_token and oauth_token_secret? I can see them, due to the webBrowser1_LoadCompleted event handler, but how can I extract them to use them in my program?
Handle the Navigating event for the browser control when it redirect back to the callback domain/Uri.
You can then extract the values you're after from from the Uri.

Resources