JSESSIONID changing every time API Fires. if same then CORS POLICY issue - reactjs

So here is the Problem.
Front end: React Js
Backend: Spring Boot (No .xml file only annotation and properties files)
What I did is first user have to login then only he can access other APIs
when user login:
#PostMapping("/signin")
public boolean signIn(HttpServletRequest request, #Valid #RequestBody UserCredentials loginData) {
return userCredentials.signInToAccount(request, loginData);
}
It will goes to this file and then it calls service class
#Override
public boolean signInToAccount(HttpServletRequest request, UserCredentials loginData) {
UserCredentials user;
HttpSession session;
try {
user = userDAO.findById(loginData.getAssociateId()).get();
} catch (NoSuchElementException e) {
logger.error(e.getMessage());
throw new NoSuchElementException(env.getProperty("signin.invalidId.error"));
}
if (user.getPassword().equals(loginData.getPassword())
&& user.getIsAssociateActive().equals(env.getProperty("active.yes"))) {
session = request.getSession(true);
session.setAttribute(env.getProperty("session.attribute"), loginData.getAssociateId());
return true;
} else {
return false;
}
}
Here I am setting the session with logged user id:
Now When I try to use any APIs like
#GetMapping("/my-tasks")
public List<Task> getAllTaskInfo(HttpServletRequest request) throws UserNotLoggedInException {
String id = session.getSessionAttributeValue(request);
return taskServices.getAllTasksDetail(id);
}
It called the session to get the id if session is not created then User Define Exception is thrown.
Here is my session file:
public String getSessionAttributeValue(HttpServletRequest request) throws UserNotLoggedInException {
String loginFirst = "";
try {
HttpSession session = request.getSession();
loginFirst = properties.getPropertyObject("session.notlogin");
return session.getAttribute(properties.getPropertyObject("session.attribute")).toString();
} catch (Exception e) {
throw new UserNotLoggedInException(loginFirst);
}
}
With Postman it is working fine.
But for Browser(edge,chrome) its nt working.
What I tried:
I tried to debug I found that with postman cookies stored with same id after login but with browser JSESSIONID is changed every time its called for APIs.
Please help me.
Thanking you in advance.
EDIT 1:
I used withCredentials:true in my api call like this
axios.post(BASE_URL_USER + "/signin", credentials,{withCredentials:true});
But now Facing CORS error
Access to XMLHttpRequest at 'http://localhost:9090/signin' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
I tried to remove the #CrossOrigin("*") to #CrossOrigin(origin="http://localhost:3000") or #CrossOrigin(origin="http://localhost:9090")
but not working
I have not applied any security filter or configuration in my backend and neither any header or cors nothing in frontend .
Just a normal call from frontend to backend.
Note: it is working perfectly with POSTMAN.

Related

Client-side cookie-based authentication with Spring Security

We have a fully-working back-end login POST service, implemented using Spring Security, along with Spring Boot and Spring Session. A user needs to be logged-in in order to access other services. The login operation works, and so does the mechanism to restrict/allow access to the other services. This has been tested with Postman, which is "smart enough" to keep the session cookie on successive requests.
Now, we are trying to build the client on React. When using the browser's debug we can see the session cookie is sent in the response header without problems. We were trying to get the session cookie from the header and store it for successive requests, but it doesn't work. When investigating we learnt we are not meant to read the response header from the code, as explained here and here.
Our login operation should redirect to /customer/home, which works in Postman but not on our application. The behaviour we get with this is a 403 Forbidden, and the way we assess it is because the cookie is not set when redirecting, and hence the second operation (GET /customer/home) fails and returns 403. Is our understanding correct? However, the browser does not seem to keep the session cookie automatically. How are we supposed to maintain the session for subsequent requests if the cookie is not set automatically, and we are not supposed to read it manually? Are we supposed to NOT use cookies for this purpose, and issue authentication tokens instead?
We are obviously misunderstanding or missing something. Any pointers please?
Our WebSecurityConfigurerAdapter:
#EnableWebSecurity
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProviderService authenticationProviderService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/customer/register").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.permitAll()
.defaultSuccessUrl("/customer/home", false)
.and()
.logout()
.permitAll()
.and()
.httpBasic();
http.csrf().disable();
}
//[ . . . ]
}
Our client trying to do a POST:
const mw = store => next => action => {
if(action.type == 'SUBMIT_LOGIN_USER') {
var payload = {
username: action.user.username,
password: action.user.password
};
// Build formData object.
let formData = new FormData();
formData.append('username', action.user.username);
formData.append('password', action.user.password);
return fetch('http://192.168.0.34:8080/login', {
method: 'POST',
body: formData
}).then(
r => (r)
)
.then(function(response) {
console.log(document.cookie) //empty
console.log(response.headers.get('Set-Cookie')) //null
next(action)
})
.catch(function(err) {
console.info(err);
});
} else {
next(action)
}
}
Using JWT (JSON Web Tokens) is a great way to implement security on single page applications like React.
If you're going with the JWT approach it would be efficient to use a package like axios to for http requests from the client side. Axios allows you to easily add an authorization token to all requests without hassle.
Even if you're not using JWT try using axios to send authorization tokens efficiently.

API cookie problems - trying to create XSRF/CSRF token in angular

My Web API method for initializing a session is successfully returning a cookie. The front end is in angular so I'm calling the cookie XSRF-TOKEN because angular says it will take that and turn it into a header called X-XSRF-TOKEN in all subsequent requests.
For reference the Web API GET controller method that creates the cookie looks like
...
HttpResponseMessage resp = new HttpResponseMessage() {
Content = new JsonContent(results)
};
if (results.Token != null) {
var cookie = new CookieHeaderValue("XSRF-TOKEN", results.Token);
cookie.Expires = DateTimeOffset.Now.AddDays(365);
cookie.Domain = Request.RequestUri.Host;
cookie.Path = "/";
cookie.HttpOnly = false;
resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
}
return resp;
...
Using Fiddler I can see the cookie in the response. Now I'm expecting 2 things
all subsequent API calls to that same domain should include this cookie in the request header
Angular should be adding the X-XSRF-TOKEN header I mentioned earlier
Neither one is happening (I'm checking with Fiddler). I've tried with the site and API in the same domain (like localhost) and in different domains.
Some sources I've checked:
https://docs.angularjs.org/api/ng/service/$http
https://stormpath.com/blog/angular-xsrf
I also tried adding
$httpProvider.xsrfWhitelistedOrigins = [webServicesPath];
where webServicesPath is a variable I use for the API path but it makes no difference.
You should use something like following
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery) {
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
}
public void ConfigureServices(IServiceCollection services) {
// Angular's default header name for sending the XSRF token.
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
}

How to enable [Authorize] on TestController on IdentityServer4 for purpose of Claim CRUD Controller

Using OIDC Client from here.
And demo server from here and here.
I have the following controller on the IdentityServer itself:
[Route("api/Test")]
//[Authorize]
[Authorize(ActiveAuthenticationSchemes = "Bearer")]
public class TestController : ControllerBase
{
public IActionResult Get()
{
var claims = User.Claims.Select(c => new { c.Type, c.Value });
return new JsonResult(claims);
}
}
If I comment out both [Authorize] attributes, I reach the TestController.
If I use just [Authorize], I get the following error:
GET http://localhost:5000/api/Test dashboard:1 XMLHttpRequest cannot
load http://localhost:5000/api/Test. No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin
'http://localhost:4200' is therefore not allowed access. The response
had HTTP status code 500.
And if I just use [Authorize(ActiveAuthenticationSchemes = "Bearer")] I get:
GET http://localhost:5000/api/Test dashboard:1 XMLHttpRequest cannot
load http://localhost:5000/api/Test. No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin
'http://localhost:4200' is therefore not allowed access. The response
had HTTP status code 500. dashboard:1 XMLHttpRequest cannot load
http://localhost:5000/api/Test. Redirect from
'http://localhost:5000/api/Test' to
'http://localhost:5000/Account/Login?ReturnUrl=%2Fapi%2FTest' has been
blocked by CORS policy: Request requires preflight, which is
disallowed to follow cross-origin redirect.
The code I use to call the Endpoint from the OIDC client is:
test() {
this.authService.mgr.getUser()
.then(user => {
// this.http.get('https://api.identityserver.io/identity',
this.http.get('http://localhost:5000/api/Test',
{ headers: new Headers({ 'Authorization': `${user.token_type} ${user.access_token}`}) })
.subscribe(res => {
console.log(res.json());
});
});
}
I am able to successfully call https://api.identityserver.io/identity with this.
This is my CorsPolicyHelper:
public class DemoCorsPolicy : ICorsPolicyService
{
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(true);
}
}
And this is where its called form Startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
...
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Resources.GetIdentityResources())
.AddInMemoryApiResources(Resources.GetApiResources())
.AddInMemoryClients(Clients.GetClients())
.AddAspNetIdentity<ApplicationUser>();
services.AddTransient<ICorsPolicyService, DemoCorsPolicy>();
}
Ultimate goal is to perform CRUD operations on permissions/claims. I am currently stuck on this seemingly trivial task of having an Authorization protected controller :/
Ultimately since I am able to to use [Authorize] successfully outside IdentityServer4, I decided to separate Authorization from Authentication and create an Authorization server which provides for separation of concerns.

asp.net web api Authorize 302 code

When I send request from client side I received 302 code and redirect to login but
next I received:
Console log: XMLHttpRequest cannot load https://login.microsoftonline.com/........................ No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://localhost:' is therefore not allowed access.
Error: Response with status: 0 for URL: null
ApiController:
[Authorize]
public string Get()
{ }
I would like to return status code 401 or something like that.
It seems like you have not enabled CORS in your API or that you are using cookie authentication instead of Token based auth.
To return a 401 instead of a 302 you could write some Custom Owin Middleware that would check what your controller is returning and alter the response to make it fit your needs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Middleware
{
using Microsoft.Owin;
public sealed class MyCustomMiddleware : OwinMiddleware
{
public MyCustomMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async System.Threading.Tasks.Task Invoke(IOwinContext context)
{
// Code here executed before reaching the controller
await Next.Invoke(context);
// Code here executed after reaching the controller, includes the response
// check response here and modify it to suit your needs
if(context.Response.StatusCode == 302) {
var headers = context.Response.Headers;
headers.Keys.ForEach(k => headers.Remove(k));
context.Response.StatusCode = 401;
context.Response.ContentType = string.Empty;
context.Response.ContentLength = null;
await context.Response.WriteAsync(string.Empty);
}
}
}
}
then in startup.cs
app.Use<Middleware.MyCustomMiddleware>();

SignalR authentication failed when passing "Bearer" through query string

I'd like to enable authentication in SignalR while the server was hosted in ASP.NET WebAPI which I'm using OAuth Bearer authrntication and the client is AngularJS.
On client side I originally pass the Bearer token through HTTP header and it works well with the WebAPI. But since SignalR JavsScript doesn't support adding HTTP headers in connection (it's because WebSocket doesn't support specifying HTTP headers) I need to pass the Bearer token through query string by using the code like self.connection.qs = { Bearer: 'xxxxxx' };
The problem is on the WebAPI side my SignalR always returned 401 Unauthorized.
Below is what I did on the WebAPI side.
1, I specified OAuthBearerAuthenticationOptions.Provider to QueryStringEnabledOAuthBearerAuthenticationProvider, which is a class I created inherited from OAuthBearerAuthenticationProvider that can retrieve Bearer token from query string. Code as below.
public class QueryStringEnabledOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
private readonly string _name;
public QueryStringEnabledOAuthBearerAuthenticationProvider()
: this(OAuthDefaults.AuthenticationType)
{
}
public QueryStringEnabledOAuthBearerAuthenticationProvider(string name)
{
_name = name;
}
public override Task RequestToken(OAuthRequestTokenContext context)
{
// try to read token from base class (header) if possible
base.RequestToken(context).Wait();
if (string.IsNullOrWhiteSpace(context.Token))
{
// try to read token from query string
var token = context.Request.Query.Get(_name);
if (!string.IsNullOrWhiteSpace(token))
{
context.Token = token;
}
}
return Task.FromResult(null);
}
}
And registered it as below while WebAPI was started.
var options = new OAuthBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = AuthenticationType,
Provider = new QueryStringEnabledOAuthBearerAuthenticationProvider(),
AccessTokenFormat = _accessTokenFormat,
};
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
app.UseOAuthBearerAuthentication(options);
2, In SignalR part I created an authorize attribute as below. Nothing changed just to be used to add break point.
public class BearerAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return base.AuthorizeHubConnection(hubDescriptor, request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return base.AuthorizeHubMethodInvocation(hubIncomingInvokerContext, appliesToMethod);
}
}
And registered it when WebAPI started as well.
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
EnableJavaScriptProxies = false
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
// Require authentication for all hubs
var authorizer = new BearerAuthorizeAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
});
I found, when SignalR connected my QueryStringEnabledOAuthBearerAuthenticationProvider.RequestToken was invoked and it retrieved Bearer token successfully. But then when SignalR BearerAuthorizeAttribute.AuthorizeHubConnection was invoked the parameter request.User still not authenticated. So it returned 401.
Can anyone give me some ideas on what's wrong I did, thanks.
I'm using headers, this is how I solved it
var authData = localStorageService.get('authorizationData');
var token = authData.token;
$.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };
Hope it helps
I resolved this problem by unprotect the Bearer token from query string in my AuthorizeAttribute, and set the user principal into a new ServerRequest. For detailed information please check http://blog.shaunxu.me/archive/2014/05/27/set-context-user-principal-for-customized-authentication-in-signalr.aspx
This might not be the best solution but it worked.

Resources