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");
}
Related
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.
I've got an app which is hosting simultaneously Identity Server 4 and a client app (Vue) which uses a couple of rest services defined in an area for managing the site. The idea is that users associated with a specific role can access the client app and call the rest services for performing the actions.
Currently, my problem is that when the api return 302 when the user doesn't belong to the admin role. I'd like to change this to a 401, but I'm having some problems with it.
If this was a simple aspnet core app, then I'd simply pass a lambda to the OnRedirectToLogin property of the cookie handler that takes care of the request. Unfortunately, IS4 will only allow me to set a couple of basic settings of the cookie (expiration and sliding). The same docs say that I can override the cookie handler. So, I've tried doing the following:
services.AddIdentityServer()
... // other configurations
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;//IdentityServerConstants.ExternalCookieAuthenticationScheme;
sharedOptions.DefaultChallengeScheme = IdentityServerConstants.SignoutScheme;
})
... //other external providers...
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.Events = new CookieAuthenticationEvents {
OnRedirectToLogin = ctx => {
if (ctx.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase)) {
ctx.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
};
});
I expected to seem my handler being called whenever a request is redirected to the login page, but it never happens. Can anyone help?
Thanks
EDIT: just to add that I'm also using aspnet identity for managing the user accounts...
Posting the answer here in case anyone is interested...
After some digging, I've found that using identity means that you can't customize the cookie handler by doing what I was doing. Fortunately, the ConfigureAuthenticationEvent that can be configured by the ConfigureApplicationCookie extension method already does the right thing: if it detects that the current request is an AJAX call, it will return 401; if not, it will return 302. And here was the problem: the request made from the vue client wasn't being considered an AJAX request because it wasn't setting the X-Request-With header to XMLHttpRequest.
So, all it was required was to configure axios to set the header in all the calls:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
I wrote a middleware sometime ago for this exact purpose and never looked back so if you don't find better solution, perhaps the solution can help you as well:
public class RedirectHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RedirectHandlingMiddleware> _logger;
public RedirectHandlingMiddleware(RequestDelegate next, ILogger<RedirectHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
await HandleRedirect(context, ex);
await _next(context);
}
private Task HandleRedirect(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase) && context.Response.StatusCode == 302)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
}
}
Just need to register in Startup.cs:
app.UseAuthentication();
app.UseMiddleware<RedirectHandlingMiddleware>();
I am trying to redirect to Identity Server's default login page when calling an API controller method from Angular's $http service.
My web project and Identity Server are in different projects and have different Startup.cs files.
The web project Statup.cs is as follows
public class Startup
{
public void Configuration(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
var openIdConfig = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
ClientId = "baseballStats",
Scope = "openid profile roles baseballStatsApi",
RedirectUri = "https://localhost:44300/",
ResponseType = "id_token token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo"),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role);
userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
// keep track of access token expiration
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
// add some other app specific claim
nid.AddClaim(new Claim("app_specific", "some data"));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
n.Request.Headers.SetValues("Authorization ", new string[] { "Bearer ", n.ProtocolMessage.AccessToken });
}
}
};
app.UseOpenIdConnectAuthentication(openIdConfig);
app.UseResourceAuthorization(new AuthorizationManager());
app.Map("/api", inner =>
{
var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
RequiredScopes = new[] { "baseballStatsApi" }
};
inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions);
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
inner.UseWebApi(config);
});
}
}
You will notice that the API is secured with bearer token authentication, whereas the rest of the app uses OpenIdConnect.
The Identity Server Startup.cs class is
public class Startup
{
public void Configuration(IAppBuilder app)
{
var policy = new System.Web.Cors.CorsPolicy
{
AllowAnyOrigin = true,
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
policy.ExposedHeaders.Add("Location");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = InMemoryFactory.Create(
users: Users.Get(),
clients: Clients.Get(),
scopes: Scopes.Get())
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
Notice that I have added a CorsPolicy entry in order to allow the Web App to hopefully redirect to the Login page. In addition, the Cors policy exposes the Location request header, since it contains the url that I would like to redirect to.
The Web Api controller method is secured using the Authorize Attribute, like so
[HttpPost]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[Authorize]
public PlayerData GetFilteredPlayers(PlayerInformationParameters parameters)
{
var playerInformation = composer.Compose<PlayerInformation>().UsingParameters(parameters);
var players = playerInformation.Players
.Select(p => new {
p.NameLast,
p.NameFirst,
p.Nickname,
p.BirthCity,
p.BirthState,
p.BirthCountry,
p.BirthDay,
p.BirthMonth,
p.BirthYear,
p.Weight,
p.Height,
p.College,
p.Bats,
p.Throws,
p.Debut,
p.FinalGame
});
var playerData = new PlayerData { Players = players, Count = playerInformation.Count, Headers = GetHeaders(players) };
return playerData;
}
The angular factory makes a call to $http, as shown below
baseballApp.factory('playerService', function ($http, $q) {
return {
getPlayerList: function (queryParameters) {
var deferred = $q.defer();
$http.post('api/pitchingstats/GetFilteredPlayers', {
skip: queryParameters.skip,
take: queryParameters.take,
orderby: queryParameters.orderby,
sortdirection: queryParameters.sortdirection,
filter: queryParameters.filter
}).success(function (data, status) {
deferred.resolve(data);
}).error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
}});
When this call occurs, the response status is 200, and in the data, the html for the login page is returned.
Moreover, I can see on Chrome's Network tab that the response has a Location header with the url of the Login page. However, if I set up an http interceptor, I only see the Accept header has been passed to the javascript.
Here are the http headers displayed in Chrome's network tab:
The response does not have the Access-Control-Allow-Origin header for some reason.
So I have the following questions:
Is there a way I could get access to the Location header of the response in the angular client code to redirect to it?
How might I be able to get the server to send me a 401 instead of 200 in order to know that there was an authentication error?
Is there a better way to do this, and if so, how?
Thanks for your help!
EDIT:
I have added a custom AuthorizeAttribute to determine what http status code is returned from the filter.
The custom filter code
public class BearerTokenAutorizeAttribute : AuthorizeAttribute
{
private const string AjaxHeaderKey = "X-Requested-With";
private const string AjaxHeaderValue = "XMLHttpRequest";
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
if(IsAjaxRequest(headers))
{
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
actionContext.Response.StatusCode = System.Net.HttpStatusCode.Forbidden;
else
actionContext.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized;
}
base.HandleUnauthorizedRequest(actionContext);
var finalStatus = actionContext.Response.StatusCode;
}
private bool IsAjaxRequest(HttpRequestHeaders requestHeaders)
{
return requestHeaders.Contains(AjaxHeaderKey) && requestHeaders.GetValues(AjaxHeaderKey).FirstOrDefault() == AjaxHeaderValue;
}
I have observed two things from this: first, the X-Requested-With header is not included in the request generated by the $http service on the client side. Moreover, the final http status returned by the base method is 401 - Unauthorized. This implies that the status code is changed somewhere up the chain.
Please don't feel like you have to respond to all the questions. Any help would be greatly appreciated!
You have probably configured the server correctly since you are getting
the login page html as a response to the angular $http call -> it is
supposed to work this way:
angularjs $http
Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning that the outcome (success or error) will be determined by the final response status code.
You are getting a 200 OK response since that is the final response as the redirect is instantly followed and it's result resolved as the $http service outcome, also the response headers are of the final response
One way to achieve the desired result - browser redirect to login page:
Instead of redirecting the request server side (from the web project to the Identity Server) the web api controller api/pitchingstats/GetFilteredPlayer could return an error response (401) with a json payload that contains a {redirectUrl: 'login page'} field or a header that could be read as response.headers('x-redirect-url')
then navigate to the specified address using window.location.href = url
Similar logic can often be observed configured in an $httpInterceptors that handles unauthorized access responses and redirects them to the login page - the redirect is managed on the client side
I have two main projects in my Web application:
WebApi project as back-end to serve authentication and authorization for the Web project,using OWIN 2 with bearer tokens.
Web project uses Angularjs.
The Web project works as expected(authentication and authorization are working)
Method: store token to localstorage, and send it using interceptors each request.
Now I want to add authentication and authorization to the the WebApi project,which would serve other modules like Hangfire,Elmah and Help pages.
I added the same login logic, which works(Authorizing) and then redirect to Dashboard page(using Angularjs) which works.
But going to any other page(one of the mentioned modules) don't work.By not working: The user from the Owin context always null/empty.(see code)
For my understanding, I need somehow to send the token with each request which doesn't happen here.
Questions:
How can I achieve that(sending/getting the token)?
If cookie is the only/better approach ↴
How can I integrate cookie for project 1 and token for project 2?(Tried to use cookies, but it seems I'm doing it wrong, or does it work simultaneously with bearer tokens?)
Code:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
GlobalConfiguration.Configure(WebApiConfig.Register);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
AreaRegistration.RegisterAllAreas();
app.UseHangfire(hangfireConfig =>
{
config.UseAuthorizationFilters(
new AuthorizationFilter { Users = "admin, superuser", Roles = "advanced" },
new ClaimsBasedAuthorizationFilter("name", "value")
);
hangfireConfig.UseSqlServerStorage("Context");
hangfireConfig.UseServer();
});
}
I tried for testing purposes:
public class HFAuthorizationFilter : Hangfire.Dashboard.IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
var context = new OwinContext(owinEnvironment);
if (context.Authentication.User == null)
return false;//Always null
return context.Authentication.User.HasClaim(ClaimTypes.Role, "SuperAdmin")
|| context.Authentication.User.HasClaim(ClaimTypes.Role, "Admin");
}
}
and in Configuration:
app.UseHangfire(hangfireConfig =>
{
hangfireConfig.UseAuthorizationFilters(
new HFAuthorizationFilter()
);
hangfireConfig.UseSqlServerStorage("Context");
hangfireConfig.UseServer();
});
Potential duplicate:
Passing and verifying the OWIN Bearer token in Query String in WebAPI
if i understood correctly, you are looking to implement token generation in one api and use the same token in other api. if that is the case then you need master api to be the token generator and child or dependent api to consume the token. Please find master and child API config for oauth
Master API config:
public void ConfigureOAuth(IAppBuilder app)
{
//configure OAuth using owin framework
var oAuthOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
Provider = new KatanaAuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(oAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
Child API config:
public void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
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.