I have a custom authorization. it uses a store procedure to check if the user has access.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OptekRequirement requirement)
{
// Log as a warning so that it's very clear in sample output which authorization policies
// Check the user's age
var userName = context.User.Identity.Name;
if(userName != null)
{
var permissions = _dbContext.OptekAuthorizedto.FromSqlRaw($"EXECUTE VerifyAuthorizationRule '{userName}','{requirement.Rule}'").ToList();
var permission = permissions.FirstOrDefault();
if(permission != null)
{
context.Succeed(requirement);
}
else
{
_logger.LogInformation("Not Authorized");
}
}
return Task.CompletedTask;
}
}
I have permission in my database like this
public static class Invoice
{
public const string View = "Permissions.Invoice.View";
public const string Create = "Permissions.Invoice.Create";
public const string Edit = "Permissions.Invoice.Edit";
public const string Delete = "Permissions.Invoice.Delete";
}
I assign the role to the user and then I assign the role with permissions. For example:
user1 is the admin and the admin has the access to Invoice page.
And I have permission for all my pages (560 pages).
If I keep adding permission to my role, at some point, I will not able to log in. And I will get a 503 error. Then If I remove some of the permission, My program begins to run again.
My theory is .net writes all permission to the cookie and then it becomes too much.
How can I solve this issue?
From the httpquest I can see this
Set cookie: .AspNetCore.Identity.ApplicationC1=CfDJ8HcqA48u9dlJhQCS1I7dylHXAGtk-B2f164O4jmKJz339TbUPtGAKoDc8RRUbf9k7JlXkPxPxtwYyrSeD80XNjHUEVSzIOIhkvvrTInvXFibgYFmygMAbJAYNw6-Ff9VyB4evNnvIe0mmZGsTp-3ZjCdxXTvecWmhli_raq84Zl9alytSiNv2Mo8FVM72D0PylAYDEf9h9iiZWGncrHR7YsbhJ1hk9WiyBx0kDxWmM1W9SLEi4rjevmibSdU_QkQd-Dg2kEBTLWlW38KgX0tkqDHbuz7ZPQOMT_EYebZyK3yY15PVR8Yjygmq2vZvkIT69-EwHY7jIOKUCt7MGlK312mm8QoB5kPDnSJHMxvyuVzcbSu-gt1aNrrryo65gV134HX4_JFcuSdWeby7f0aeRNGPQJ4flUn4A-t18AGoIdweshiNb4JNlvXmUy_tBzph7gE1dlfIwfckksw5nDsJPzg5JXECPVajpheUGgETSejtIp1f5kyu18RKW7b0NL5e2TbN3PPCeo_lWEW1MGYYhxwZbfJ6BXhmb9M_KzQWqNeSQEndwi5jJ9u-rIW4q2roTg4lZ3B5sc81lTsbW9Jxg2mv4NvdVifBvb5l1dU4ccBvdADcFapY67OWN0C6BqVzl7rVegXJ4jfcmSL_LFiOzDQlrVsxGu2YyBMZcuDLqmWs2QzHU4NVyWgcz_Rkr-YgfDMsP2upaLLB5wJ0Iv33Pyg81QJ-LdsR6qvUvi2OYGsma9tIjBvalgK61PiJESdCpzKG4ZiZMaY6vQ1Zs9ZDZKJqJacWS-aszMc5ALl89jhaz6ROkmlF_Qg_CJ64yJUK5_BtOI_SSjQzNHD8Y1PeNUdy_Y-eTqS1IbQyyjSLOX3WNb_KqOJYlNAtA8cqPrgxzkfjvs0IG-gRfPSF1_9sw5EHbpAuM1zpPO5hxzda3_9vcEt-qubjyRIx3u95hM8EtzKJPb89h6_O6lyW57AQxHH7ui1zLt4AE4e0eL7k_c5-hi09eAzP0FgR_HyGyGGJW9szl41PJFDbb7Plr02vcJKRaPBbfxDnyqhw573SOkIZLDgELRgkDVoiNsgKGTFcxZTxx8JtETXsQ9g2lm1BwxVxADvM68rAMQjjpMzLHkk768RGk6anJ48fErKNVK4nn5Ex7N4hIx0fpQDPLWG46BNDVOemTx3x6z4JgjM5EtsRG6QoFIT-4Z453rrFazUt-84QXvCAy1KV85LYkRXD9TrUmheAgWVca_da5WcPYrB3TsNOqOzqv2aGr44ONyaXjmxmXhDtQjGBk-NN-ueIfSGtzrI2UuGFbh5w6cvXvM5obYMNDP07B0jF8hy7Lu5OG3PyH4T7Buk5fLS5c1R6ZYPbrzTLNUIBwkIzGb8_AeOV-L4fFbbtLiPFNKBqSVr9jQSkxE35cgF-rSvzfuC5-wRgJSUO2mnag4XysMNLYZSwLKSir6eaDPuzyYfJ8q0IbqUZRNn8fnuIaHSIUB_tRJqmxOlfUIohiUeXOyvoMwyL1iTevK6joghgvO8nQteXMfNPUNHs2bg7u0br_mUeEVAxargeszimtDD1xgcC5o3urUPtxbgk1a_Ks3hvVzdHW4_p4RLWrmwm4IiZb3zZayYYGy90uiHOQpCXtXeFKLq5qLU7SVJ7Jl-__8Q8S_JXvsIrq8DSuhQ-lyqbSUuS6k3emb9124Y2D5j4al67N53cm8-ho-7J-bDGD4PWRGsb_lNZr6tr4OIZRkFiICgwDVhDSCzTqyXFz6kGyTpnN1g0u_DCndiiM8gBDAFQy1AWqcI4RptSclWyyvt_IqQMq1CVQ-fRxppifUJf8fN5nEmOSwnJW7y7FBRuHorSvo9SAnf7NlZCeoMmmF0-Lq1ZmqR7CbUlbnI6vAfmFiGhybMXPoBt1ogCbNpskGIgDGBmPMPYyYLfvXphvEvDH8hXBRHhaDjFjLYsnvuzFgIMBASyaBiUGAc8gOvrwSfd81U2tDUSiSiMa7n84n2gnoXhEvR33blF197HJsAknK6HANNQZsaJ7gNhkIZeKK5VmC2BHcoWMv-MqDXngPGzNPj8pNb0hXsB5QiuafRoNI8FGTFwEyc6KIad7jBnbs4vt_7bHlXri79YIlAABwfpJdk7ZQFuMcpYASkWT7mhwwpysyn021YVj_w2iASPwdRh_vJDTbMcGWZsrBYM5E7SbHPDC4w8T_7aX6Z4eSo1ENDfp-G54b9VA3Bz-D_XIk7D-_F59jFUyH8-KmhA2qPBr6Ictyb89ZJboTmUdxRDs3wojbcztfYDFBd1ICn90IsS1a8Ffq9KnxhfudIKtxZRd99mlssRxXR0e5J4V-wq5xtAvkT-PPCcQTon1tKj7kLlTCqbzZl1neU1yoZ_7jAmETyB5_CyBRfz9omCHKLe3Bp_dIKJlFLukKetHslc1USxFS2gkLvuhdwn2HxnF3bdYMKfJ1sj9D8V4_AHm5cKvrOpXFEmwO_nvP3hL6_YJbc4ApKxZdR4UaZGBXl9Uh3eaCiwmccOmtVKx9fjQySj7QCcXB-rsW1YivE1qCyrlDrd7vSDvD4pSRJEmJ4ySMhdN_zpZKIuM4257Ji04yiERGiMgCZQJASlP5WwLAbbewSyJt8sBmaAAxLDWKxTB_1LUaaOLzoILqqSj3cj0wof4uHWEawXWd56qGIwvMcgsrvx3GqNwwvPk276fed4ZjlTltitc-YHsM7gw3tE8DuKgPbbR__nRRhmMadVBhoNkUayyF3doErQ7iEVrWVmlmx8nyLEhjQJxPA554CRObkZA37HVsIlj2bHm0SCyY6o8GD860uuArktUA8U50UwbgRj-TUZjiDOcdGemqVfgsUCCE45SmlVGlsyk-JUOu1G68RwPXBZSTr_9oD7SIaZfjW3kaDQ6h3jjf6zeXM525OWCTHp-_hdAnqvDMpoyag5QIJwNN2cnsmHZH8M3GIzABPBvdTJaR78LyhA1fDMDZGZt5eB_Iyin8fYjzjx7Db5kpm1mOeV_sG0cOhYtv_P4Qajm3FvQjctK8_I4fx3TAgZ6q_vQxn8v2muJswVdOWzYBJI5dJ-Ys_RglKU1xS0P9ZOLceP_dvGZvsRrs54gI0PkITKhr_4JZ5yW9nd0RoTEglFCzA90hhUjX2p8Up1D6kREwUpqK96AZzSkpHBVU30RGP7caoqk3dLUS03u9v7A-rzIH3QpmAZ8IQbBDUf2VTJziwaX4wPfiHf3gktCKYxfjm-QFIiLgwKj9r6VI_N33-bqjGXcwfkQwEw5nKyWAt74IKM2RpIIYRxocF0rR2lg0K5YQA_mTx1hLqfKp8--EsUujZqU3IOC-lwvWtDQjubcguc0soKSBzrwD-pdz9dtPv2yOHEH3W9L6ODNUnvKjhHID-YRjjXoTN5e3ndRfo0I31T8GcIazi5Ud53fIrB6fVdYfFxBYlSlibxQyUFp7hgrK5GlMrYQNzTANHvuYIkuWgQ6QMUlnEUdYgjkxj_Oyn9J8NsAgMV4FsQlOk-vD3DVBNbwtjGBD-xaTOXaHgSnk0F-W93gMZtwu8Tk5s1FXVpPIjR-FnmfofMeXHwoNp2EhEE4p1IP6vkSuMKRV5lEYNnPJiCpJUsU-5X3j_Ugoz6fufx9MfeZlhmH6FbxhfFoX2EJfA7JEhoTaFlTYW65d0YGe_ndHyOHyNpLXWlG2phFWTswHQ4QYMONGCLVkmui9cgbcXR-dscSwUG3rOFcdTHPgposqm6CVs2X1YIqRG_JwINlGnsdKjMBscyaeg-H-Oa2XKklfqNcTDLe2WULrH2MiswQG6bF1nZ-wt7gP_a4YYfvR1eBSKAMMjn5NK7X0SQqlehsIAgwAm1Ds5OdjgHFaVaYFVjdnPLdxubpxydPCumYHmO8pPSI2CbsGWYoX7g7hXbrevzRCYpTUNu29S4cTZ3fq; expires=Thu, 04 May 2023 16:59:48 GMT; path=/; secure; samesite=lax; httponly
I try to assign less permission to the role and my program starts to work again, but i dont know how to solve this issue
The issue happens because the entity framework core default log-in method loads all the userRole-Permissions into the cache. In my project, I don't read the cache to get permission but I do a database query. So to solve this I create a separate table to store the Role-Permissions, so the default log-in method will not read my permissions and cache them.
Related
I am currently trying to replace our company wide user authentication that we use for all our internal web apps and what not as our current one was made in 2006 and fails on the regular. I was told to make it as simple as possible to implement on all existing projects. It is a .NET class library. It's .dll will be added as a reference to existing projects.
I am having an issue where I can log in exactly one time after all cookies have been cleared. Once I logout and log back in I get System.ArgumentException: Invalid value for 'encryptedTicket' parameter. I found some posts suggesting the cookie may be null, or I'm not trying to decrypt the name and not the value, but that wasn't the case. This happens on chrome and edge.
The user is authenticated every time though, assuming the correct username and password is used as I get redirected to the success page.
After authentication I add a cookie and then redirect.
private void AddCookie(int compID, bool persist, HttpContext httpContext)
{
httpContext.Request.Cookies.Add(SetUpSession(compID, persist));
FormsAuthentication.RedirectFromLoginPage(compID.ToString(), persist);
}
My method for creating the cookie
private HttpCookie SetUpSession(int companyID, bool persist)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
companyID.ToString(), // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
persist, // true to persist across browser sessions
FormsAuthentication.FormsCookiePath); // the path for the cookie
String encTick = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie("Cookie", encTick);
cookie.HttpOnly = true;
return cookie;
}
After I redirect to the success page there is a snipped of code that checks to see if the user is logged in. This is where the error happens
public dynamic isLoggedIn(HttpContext httpContext)
{
AuthenticationUtilities authUtil = new AuthenticationUtilities();
if (httpContext.Response.Cookies["Cookie"] != null)
{
companyID = authUtil.Authenticate(httpContext.Request.Cookies["Cookie"]);//the error occurs here
authUtil = new AuthenticationUtilities(companyID);
return authUtil;
}
else
{
httpContext.Response.Redirect("~/login.aspx");
return null;
}
}
The method that decrypts the cookie
public int Authenticate(HttpCookie cookie)
{
FormsAuthenticationTicket authTick = FormsAuthentication.Decrypt(cookie.Value);
return int.Parse(authTick.Name);
}
this method is called on any page that requires the user to be logged in, like this.
LMFJAuth.AuthenticationUtilities auth = _LMFJAuth.isLoggedIn(HttpContext.Current);//if the cookie is null it redirects to login.
This is the logout method
public void LogOut(HttpContext httpContext)
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie("Cookie");
cookie.Expires = DateTime.Now.AddMinutes(-1);
httpContext.Session.Clear();
httpContext.Response.Cookies.Add(cookie);
httpContext.Response.Redirect(FormsAuthentication.LoginUrl);
}
Can somone help explain what may be going on in which the value for the encrypted ticked is coming up as invalid after the first successful login/logout?
For me it was that the encrypted value of cookie.Value was coming up as greater than the maximum value of 4096, being 4200 in my case. I had just added some role strings to the user data.
I found it help to look up the source code of Microsoft classes when I'm stuck, in this case I used:
http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/xsp/System/Web/Security/FormsAuthentication#cs/1/FormsAuthentication#cs.
In our developing e-commerce solution we are using AspNet Identity 2.2.1 and it is required that any guest (anonymous) users should complete checkout without prior registration to the website. In order to fullfill this requirement have written an ActionFilter named UserMigrationAttribute which obtains SessionTrackId (string GUID) from cookie -which we set from a HttpModule for every request if SessionTrackId is not found along with request cookies- and creates and actual IdentityUser in database with the username something like SessionTrackId#mydomain.com.
We have decorated our BaseController class with this UserMigration attribute in order to utilize its functions throughout the site.
Everything up to this point works as expected with single downside issue, which is when the page is being loaded for the first time for any user, if we try to make an Jquery Ajax Call to a Method which have [ValidateAntiForgeryToken] attribute, the call fails with the 'The provided anti-forgery token was meant for a different claims-based user than the current user.' error, even though we are sending __RequestVerificationToken parameter with every ajax call.
But if user opens another page by clicking link and/or reloads/refreshes current page, all the subsequent ajax calls complete successfully.
In our understanding UserMigrationAttribute creates user on OnActionExecuting method, but after we signIn user in the process #Html.AntiForgeryToken() is not being updated with the right values.
You may find the UserMigrationAttribute code below;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
Any help or suggestions are highly appreciated.
Thank you,
This is happening because the anti-forgery token is set in a cookie, which will not be updated until the next request. If you're manually signing a user in, you should also issue a redirect (even if to the same page they were already headed to), simply to ensure that the cookie data is correct. This normally happens naturally, as the sign in form will redirect to the URL that needed authorization after the user is signed in, thus negating the problem. Since you're not redirecting currently, the data is out of sync.
However, I have to say that this seems like a very poor solution to this particular use case. Creating some sort of temporary-type user and signing that user in to handle guest checkout creates an unnecessary glut of useless data in your database, at best, and leads to bugs and other issues like this one you're experiencing, at worst.
I also run an ecommerce site, and the way we handled guest checkout is incredibly simplistic. The checkout data is just stored in the session (email, shipping/billing address, etc.). We build a view model to handle the actual checkout where the data necessary for submitting the sale comes either from the user object, if they're logged in, or these session variables, if they aren't. If the user is neither logged in, nor has the requisite session variables set, then they are redirected to the onboarding form where billing/shipping, etc. is collected.
For other aspects like maintaining an anonymous cart, we use a permanent cookie with the cart identifier. If the user ends up creating an account, we associate the anonymous cart with their user, and then remove the cookie. This ensures that their cart survives past the session timeout and things like closing the browser, even if they're anonymous.
In other words, in all these things, no user object is actually needed. If it's there (user is logged in), great, we'll use it. Otherwise, we collect and persist the requisite information for checkout via other means.
I have made my API's secure using Authorize attribute.
If the request does not have valid OAuth token, it does not process and returns authorization error.
In my User table I have added 'Status' column to check if the user is Active(Soft delete) or not.
I want to use that 'Status' value(Of User Entity) to authenticate my API's.
Suppose Status is false, I want framework should not process the request as like in case 'Authorize attribute'.
Can it be possible ? If so then How ?
Note:
Now I am checking 'Status' value in my Action and decide to proceed or
not.
If you have other best alternatives then those are also welcome.
Thanks.
You could implement your own AuthorizeAttribute
Here's an example I used to implement my own Authorize method based on Roles:
public class AuthorizeRoles : AuthorizeAttribute
{
private readonly List<string> _rolesAllowed;
public AuthorizeRoles(string rolesAllowed)
{
this._rolesAllowed = rolesAllowed.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller;
if (!this._rolesAllowed.Contains("*"))
{
string errorMessage = "Authorization denied. Missing required role";
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
else
{
string errorMessage = "Authorization denied. Request is missing context header";
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
base.OnAuthorization(actionContext);
}
you then use it in your api methods like this :
[AuthorizeRoles("some value")] or [AuthorizeRoles("*")]
You could effectively specify which methods allow access to people with a certain value in their status.
You have access to the controller, you can check any values you need and allow or deny access based on the results of your check.
I have removed some of my less interesting code but what's left should give you an indication of how to proceed.
How I would do it is I would create a Roles table with different access levels. On my controller I would use the Roles feature of the Authorize attribute to allow or block users from accessing the Action.
[Authorize(Roles = "isActive")]
I am working on a cross platform web app using angular and webapi. The problem is when the angular app runs in a cordova container. To play nice with the rest of the applications on the device, I am required to use a plugin for SSO.. This plugin is what is causing me issues, because it does a few things. It intercepts all the http requests and adds a bearer token to the header, which is generated by a 3rd party Token provider, so I can't decode it, and overwrites any bearer token I have set in the header.It also seems to block cookies..
So it makes it a bit tricky when you can't send you own local credentials.
So I started with https://coding.abel.nu/2014/06/writing-an-owin-authentication-middleware/ and http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationHandler.cs
So I figured I should write my own middleware to take care of this; I thought since the standard oauth middleware can work without cookies, I should not have too hard a time getting my slightly different bearer token middleware to do it.. But that has not been the case... Writing my own middleware.. so I'm able to get the header, validate with the external token provider, but I can't actually sign in.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
// Find token in default location
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
}
}
.... Take the Request token call other Server, verify token...
Also
public override async Task<bool> InvokeAsync()
{
var ticket = await this.AuthenticateAsync();
if(ticket != null)
{
this.Context.Authentication.SignIn(new AuthenticationProperties(), grantIdentity);
return false;
}
}
So in the end the SignIn does not cause a error or anything, but does not actually signin. As soon as I get to a controller action with an [Authorize] attribute, I get a 401. I not have any external cookies enabled. There is a high probability that I am on the wrong track or I am making it way too hard.
You are doing it way too hard.
Instead of creating your own bearer authentication middleware you should change the default OAuthBearerAuthenticationProvider.
Here is a sample for sending the token in the query string.
//in Startup class
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
//your settings
});
//implementation
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
private const string AccessTokenQueryKey = "access_token";
public override Task RequestToken(OAuthRequestTokenContext context)
{
//check if token found in the default location - "Authorization: Bearer <token>" header
if (string.IsNullOrEmpty(context.Token))
{
var token = context.Request.Query.Get(AccessTokenQueryKey);
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
}
return Task.FromResult<object>(null);
}
}
So… I ment to answer it earlier, but I was able to figure it out, without override the authorize attribute. I ended up looking at the source for the OWIN security code. The trick is, you really need 2 OWIN middleware components. One is the what I call (and I stole this from the owin source) the server middleware. The server middleware responds to the challenge and/or if you are feeling crazy generate local credentials for you. This middleware is also a PASSIVE middleware component. I won’t get in to generating the local credentials unless someone asks , because it’s a bit off point, but if someone thinks it will be helpful, I can update.
public class LowCalorieAuthenticationServerHandler : AuthenticationHandler<LowCalorieAuthenticationServerOptions>
{
//Important this needs to be overriden, but just calls the base.
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
return Task.FromResult<AuthenticationTicket>(null);
}
/// <summary>The apply response challenge async.</summary>
/// <returns>The <see cref="Task"/>.</returns>
protected override async Task ApplyResponseChallengeAsync()
{
if (this.Response.StatusCode != 401)
{
Task.FromResult<object>(null);
return;
}
var challenge = this.Helper.LookupChallenge(
this.Options.AuthenticationType,
this.Options.AuthenticationMode);
if (challenge != null)
{
//OK in here you call the rediret to the 3rd party
//return a redirect to some endpoint
}
Task.FromResult<object>(null);
return;
}
}
Anyway notice how the override AuthenticateCoreAsync() just returns
return Task.FromResult(null);
This is because we don’t want this middleware to modify the request. ApplyResponseChallengeAsync will wait for a Challenge and redirect you to the 3rd party login. IF you want to create a local token of some sort you would override the InvokeAsync method
The second middle ware you need is the token/external credentials validator. This will then authenticate the user somehow. In the case of the local bearer token that is built into the OWIN security, it simple deserializes the token and if it can, and the token is not expired it authenticates the user. So in the case that you want to verify the token with a 3rd part sso, such as google or anything, you insert you logic here. In my case I not only wanted to call the 3rd party provider to get the user info, but to check if they token was still valid for single sign out, and to prevent multiple sessions.
public class LowCalorieAuthenticationHandler : AuthenticationHandler<LowCalorieAuthenticationOptions>
{
//Going to give you the user for the request.. You Need to do 3 things here
//1. Get the user claim from teh request somehow, either froma header, request string, or cookie what ever you want
//2. validate the user with whatever user store or 3rd party SSO you want
//3. Generate a AuthenticationTicket to send to on to the request, you can use that to see if the user is valid in any Identity collection you want.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
//Good to throw in a point of override here.. but to keep it simple-ish
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
//TOTAL FAKEOUT.. I am going to add a bearer token just so the simple sample works, but your client would have to provide this
authorization = "Bearer 1234567869";
//STEP 1
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
return await FakeExternalBearer(requestToken);
}
return null;
}
private async Task<AuthenticationTicket> FakeExternalBearer(string token)
{
var authenticationType = Options.AuthenticationType;
//pretend to call extenal Resource server to get user //STEP 2
//CallExternal(token)
//Create the AuthTicket from the return.. I will fake it out
var identity = new ClaimsIdentity(
authenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier,"user1", null, authenticationType));
identity.AddClaim(new Claim(ClaimTypes.Name, "Jon",null, authenticationType));
var properties = new AuthenticationProperties();
properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);
properties.IssuedUtc = DateTime.UtcNow;
var ticket = new AuthenticationTicket(identity, properties);
return ticket;
}
}
Ok here we override AuthenticateCoreAsync, but we actually do something now. This this were your do you user authentication. This is the ACTIVE part of the middleware. Note it needs to return a valid AuthenticationTicket. This will run on each request so be careful what you call and how often.
So I have a very simple example here https://github.com/jzoss/LowCalorieOwin If anyone is interested in more detail, please ask. I can add more. I did make it too hard, because now that I understand it, it’s pretty easy, but there is really no good examples on how to do this.
I am new to webapi and mvc and I am struggling to find a best practice for handling authorizations dynamically based on roles and ownership of the resource. For example an account page that should allow employee admins, employee call center or the owning client to Get, Post, Put or Delete account information. So an admin and call center employee should be able to Get, Post, Put or Delete any request for any userid, but a client should only be able to perform these actions on resources owned by them.
For example Tom is UserID 10 and Jerry is UserID 20.
/api/Account/10 should be accessible by any admin, call center or Tom. Jerry should be kicked out.
/api/Account/20 should be accessible by any admin, call center or Jerry. Tom should be kicked out.
In webforms the typical solution is to just check if the user is a client and verify their id against the request. (I know AuthorizeAttribute is not in webforms, but showing as an example of what it would covert to in webapi/mvc.)
[Authorize(Roles = "Administrator, CallCenter, Client")]
public string Get(int userID)
{
if (Thread.CurrentPrincipal.IsInRole("Client") && Thread.CurrentPrincipal.Identity.userID != userID)
{
//Kick them out of here.
}
return "value";
}
This will work, but it seems like the check for ownership should happen in a single location before it reaches the controller and should be reusable throughout an application. I am guessing the best place would either be a custom AuthorizationFilterAttribute or a custom AuthorizeAttribute and maybe create a new role ClientOwner.
[Authorize(Roles = "Administrator, CallCenter, ClientOwner")]
public string Get(int userID)
{
return "value";
}
Custom AuthorizeAttribute
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//If user is already authenticated don't bother checking the header for credentials
if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { return; }
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
!String.IsNullOrWhiteSpace(authHeader.Parameter))
{
var credArray = GetCredentials(authHeader);
var userName = credArray[0];
var password = credArray[1];
//Add Authentication
if (true)
{
var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
var user = GetUser(userName);
foreach (var claim in user.Cliams)
{
currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, claim);
}
//**************Not sure best way to get UserID below from url.***********************
if (user.userTypeID = UserTypeID.Client && user.userID == UserID)
{
currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, "ClientOwner"));
}
Thread.CurrentPrincipal = currentPrincipal;
return;
}
}
}
HandleUnauthorizedRequest(actionContext);
}}
Can someone point me in the right direction as to the best place to handle the authorization of the individual user? Should this still be done in the controller or should I move it to a custom AuthorizationFilterAttribute or a custom AuthorizationAttribute or is there somewhere else this should be handled? If the proper place is in a custom attribute, then what is the best way to get the userID and should I create a new role like the example above or should I do something different?
This is a common scenario and I am very surprised that I have struggled to find examples of the above scenario. This leads me to believe that either everyone is doing the check in the controller or there is another term I am not aware of so I am not getting good google results.
I think you may be getting authorization and permissions confused. "Dynamic authorization" isn't something you ever do.
Authorization is the act of verifying an author.
Request claims it is being sent from Alice.
Request presents a password or authorization token that proves the requester is Alice.
Server verifies that the password or authorization token matches its records for Alice.
Permissions are the business logic that specifies who can do what in your system.
Request is already authorized, and we know it came from Alice.
Alice is requesting to delete an important resource.
Is Alice an administrator? If not, tell her she can't do that because she doesn't have permission. (403 Forbidden)
The built-in [Authorize] attribute lets you optionally specify Roles that are permitted to access a resource. That option to specify permissions as part of authorization is slightly misplaced, in my opinion.
My advice would be to leave authorization as purely the process of verifying the author of a request. The BasicAuthHttpModule described here is close to what you want already.
Non-trivial permissions logic needs to be handled inside of your action body. Here's an example:
//Some authorization logic:
// Only let a request enter this action if the author of
// the request has been verified
[Authorize]
[HttpDelete]
[Route("resource/{id}")]
public IHttpActionResult Delete(Guid id)
{
var resourceOwner = GetResourceOwner(id);
//Some permissions logic:
// Only allow deletion of the resource if the
// user is both an admin and the owner.
if (!User.IsInRole("admin") || User.Identity.Name != resourceOwner)
{
return StatusCode(HttpStatusCode.Forbidden);
}
DeleteResource(id);
return StatusCode(HttpStatusCode.NoContent);
}
In this example, it would be difficult to convey the permissions logic as an attribute on the action, because the portion of the permissions that compares the current user to the resource owner can only be evaluated after you have actually gotten the resource owner info from your backend storage device.