Currently I’m trying to implement activity-based authorization in my webform application. In my application you have different roles: Admin, manager, employee, and trainee.
The application communicates with my database through my BLL (business logic layer) and what I’m trying to do, is have authorization on method level in the BLL for all the reader and manager files for all my entities, like so:
[Authorize(Activity = "CreateUser")]
public static void InsertUser( ... )
[Authorize(Activity = "UpdateEmployee")]
public static void UpdateEmployee( ... )
For every method I want a CRUD activity and in the database I want to tell which roles gets to do what activity, or something that´s a lookalike like this.
Role-based security is not an option, besides for logging in, because a trainee for example is not allowed to insert or delete any data, except for one entity. So I need a permission/activity based authorization in my webforms.
Currently all I can find are solutions for MVC and .NET Core, but not any for the webforms, or the posts are from 2011 ish which don't really offer a solution, and I wonder if anyone has any new ideas or solutions as of this time.
In my project I make use of FormsAuthentication, and in my webconfig file I autorize specific roles and paths for specific roles. But this won’t cut it. I also make use of the Application_OnPostAuthenticateRequest in the global file with the FormsIdentity with my custom principal class which makes use of the IIDentity and IPrincipal.
Also, my AddEdit pages are the same page, and based on the querystring passed in the URL, like a userId, or employeeId, the save button decides whether it's an insert or update. So this is the reason why I need auth on methods.
So my question, does anyone know a pattern that I can actually implement in asp.net 4.5 webforms that gets permission or activity based authorization without having to hardcode everything so it's more future proof?
Related
I'm looking to understand the nitty gritty mechanics of authorization so I can devise a strategy for my situation.
My situation is that I am part of a distributed application. My part is an MVC5 application that basically just consists of a couple of controllers that return single page app views. So hit controller A and get back single page app A. Hit controller B and get single page app B. Etc. This application contains no database or user data. Some other application on a completely different website/server does. I want to ask that other application if a user is valid or have users ask the other application directly themselves and only allow access to my app views if the answer is yes. So, in essence, I want to protect my controllers based on the word of a remote application that contains an exposed api for login/user validation.
It has been suggested to me that token authentication is the way to go. It's a bit daunting with my lack of experience with it, but I've buried myself in some reading and video presentations. Here is my current, weak attempt at summarizing the task based on limited understanding. Please correct as needed:
An access token needs to be generated
Getting an access token is not part of the Account controller, it's part of OWIN middleware
The access token will be sent along with the requests for my contoller actions
My controller actions, decorated with the [Authorize] attribute, will parse the token and do the right thing
Questions:
Q1: Should I generate the token or should the other app - the one with the db and user data?
Q2: My controllers don't know anything about users. That data is in the other app. What specifically are the controllers parsing for under the hood in order to do the right thing? In essence, what specifically tells them, "yes, this request is OK. Return the view."
Q3: I started my project awhile back using a standard MVC5 project template that comes with VS2015 because I figured I'd be handling users/login etc. That turned out not to be the case. Am I going to have to go back and redo this project from scratch because that template doesn't fit this requirement or can I do some surgery on it and continue? For instance, I don't know if that template has all the OWIN stuff I need or maybe has too much extra junk (bloated Account controller, Entity Framework, etc.) to be easily transformed/maintained.
Q4: Is token authorization overkill here? Is there an easier way to keep unauthorized users from accessing my controller actions that makes more sense given the nature of the project?
Any insight would be appreciated.
Update: What I meant in Q2 was, at it's simplest, how does [Authorize] work? Details? I'm guessing I have to tell it how to work. For instance, a silly example to illustrate. If I wanted to tell a controller decorated with [Authorize] to let anyone in who has the username "fred", how and where would I do that? I'm not so much looking for code. I'm thinking conceptually. My app must know something about the tokens the other app (authenticating app) is genenerating. In general terms, what would I add to my MVC app to tell it how to decode those tokens? Where do I add it? Is there one standard place?
I think you are on the right track and are right about the steps you have mentioned. I will answer your questions based on what I understand:
Q1. The other application is the one that needs to authorize and generate a token (whatever be the authorization mechanism they use) and you should receive this token before showing your views. Since the data is coming from the other application , they have to give your controllers access to their data. This is why you need to ask the other application for the token/authorization. With a valid token got from the other application your application can send valid and authorized requests to their data.
Q2. What you can do from your side is to add a check as to whether the request for your action/view is coming from an authorized user. For this, you need to check if this request has a valid token.
Q3. I don't know what you mean by "template" here. But if you need to integrate your controllers to the other solution, you do need to know what the other solution does and what it offers in terns of authorization and of course the data. They should provide your application access to a public api for this purpose.
q4. THis is something the other application needs to do. From what I understand, I think you are only adding a web API to an existing system so I think you need to really know how you can integrate with the other application. They should have clear APIs that are public for you to do this to access their features and data.
Since you have not mentioned if this other application is something like a secure enterprise solution or a Google API (has public API ) it would be difficult to tell exactly what you can expect from the other application.
I think you would need to try JSON web tokens (JWT )
I have not used it myself though . stormpath.com/blog/token-auth-spa –
It is useful for authenticating if a request to your controller. Here is a similar question as you have (I think) and how JWT could solve it How to use JWT in MVC application for authentication and authorization? and https://www.codeproject.com/Articles/876870/Implement-OAuth-JSON-Web-Tokens-Authentication-in
You can override the AuthorizeAttribute like this : https://msdn.microsoft.com/en-us/library/ee707357(v=vs.91).aspx . Your authorization logic of checking for whichever tokens/auth mechanism you decide to can be added to this new action filter. Then add that new attribute to your actions. So if your custom authorization attribute when overriding looks like this:
public class RestrictAccessToAssignedManagers : AuthorizationAttribute
Then your action would have the attribute decoration like this:
[RestrictAccessToAssignedManagers]
public ActionResult ShowallAssignees(int id)
Found a good article which could also be of help - https://blogs.msdn.microsoft.com/martinkearn/2015/03/25/securing-and-securely-calling-web-api-and-authorize/
My answer to your question will be based on:
I want to ask that other application if a user is valid or have users
ask the other application directly themselves and only allow access to
my app views if the answer is yes. So, in essence, I want to protect
my controllers based on the word of a remote application that contains
an exposed api for login/user validation.
Yes, to my humble opinion, oauth token-based approach is overkill for your need. Sometimes, keeping things simple (and stupid?) is best.
Since you are part of a distributed application, I suppose you can (literally) talk to the team in charge of the "other application/website" where requests that hit your controllers will be coming from.
So, I will skip your questions 1, 2, 3 and 4, which are oriented towards the token-based oauth approach, and suggest a rather different, "simplistic" and faster approach, which is the following:
For clarity purpose, let's call the other application "RemoteApp", and your application "MyApp", or "The other team" and "You", respectively.
-Step 1: You (MyApp) exchange a symmetric secret key with the other team (RemoteApp);
-Step 2: MyApp and RemoteApp agree on the algorithm that will be used to hash data that will be posted to MyApp when a user from RemoteApp requests a page on MyApp. Here, you can, for instance, use MD5 or SHA256 algorithms, which are well documented in MSDN and pretty easy to implement in C#;
Step 3: MyApp tells RemoteApp what its needs to be part of the posted data to validate/authenticate the request.
Here is an example:
Step 1: BSRabEhs54H12Czrq5Mn= (just a random secret key. You can choose/create your own);
Step 2: MD5 (this is the algorithm chosen by the 2 parties)
Step 3: The posted request data could include (at least 3 - 4 parameters or form fields, plus the checksum):
- UserName+RemoteApp fully-qualified domain name + someOther blabla data1 + someOther blabla data2 + checksum
The above information will be concatenated, without space. For instance, let's assume:
UserName = fanthom
RemoteApp fully-qualified domain name = www.remote.com
someOther blabla data1 = myControllerName
someOther blabla data2 = myActionName
The checksum will be generated as follows (function prototype):
generateMD5(string input, string secretKey)
which will be called with the following arguments:
string checkSum = generateMD5("fanthomwww.remote.commyControllerNamemyActionName", "BSRabEhs54H12Czrq5Mn=")
Notice that in the first argument the above 4 parameters have been concatenated, without space, and the second argument is the secret symmetric key.
the above will yield (actual md5 checksum):
checkSum = "ab84234a75430176cd6252d8e5d58137"
Then, RemoteApp will simply have to include the checkSum as an additional parameter or form field.
Finally, upon receiving a request, MyApp will simply have to do the same operation and verify the checkSum. That is, concatenate Request.Form["UserName"]+Request.Form["RemoteApp fully-qualified domain name"]+["someOther blabla data1"]+["someOther blabla data2"],
then use the md5 function with the secret key to verify if the obtained checksum matches the one sent in the request coming from RemoteApp.
If the two match, then the request is authentic. Otherwise, reject the request!
That'all Folks!
I seems you need to implement an OpenID/OAuth2 process.
This way, your apps will be able to utilise single-sign-on (SSO) for all your apps, and all you would have to do is set up your MVC5 app as an OpenID/OAuth2 client.
Take a look into Azure AD B2C which is perfectfor this (I am currently implementing this right now for 3 projects I am working on).
https://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
https://azure.microsoft.com/en-us/services/active-directory-b2c/
https://identityserver.io/
So your app is publicly addressable? I can't tell for sure from your description.
Well you only have these issues if a public client is requesting certain page views from you...
Now here's where i'm confused. If it's an external client accessing different parts of your distributed app, ALL the parts have this problem.
Generally the client authenticates itself at one place (say written by Team A), any other subsequent view requests would need to be checked as well (HTTP being connectionless/stateless), including others done by Team A? So your problem would already be solved (because it would be a problem for everyone, and they would have done something using an auth cookie + checking service, use the same checking service...)?
Which leads me to believe that the view requests are internal and not client facing, in which case... why is auth such a big deal?
If you could clarify your scenario around what requests you need to authenticate...
you are on right track. But instead of you implementing OAUTH and OpenIDConnect user third party which does the heavy lifting. One such tool is IdentityServer
https://identityserver.github.io/Documentation/docsv2/
Now answering your question from IdentityServer point of view
An access token needs to be generated -- true
Getting an access token is not part of the Account controller, it's part of OWIN middleware -- yes, for better design
The access token will be sent along with the requests for my contoller actions
My controller actions, decorated with the [Authorize] attribute, will parse the token and do the right thing -- Yes as a part of response header
Questions:
Q1: Should I generate the token or should the other app - the one with the db and user data? The identity server will generate token that you requested.
Q2: My controllers don't know anything about users. That data is in the other app. What specifically are the controllers parsing for under the hood in order to do the right thing? In essence, what specifically tells them, "yes, this request is OK. Return the view. - usually the token is sent back to the identtyServer to check for validity and to get access_token which will check if the user has access rights. if not [Authorize] attribute will throw error message and return
Q3: I started my project awhile back using a standard MVC5 project template that comes with VS2015 because I figured I'd be handling users/login etc. That turned out not to be the case. Am I going to have to go back and redo this project from scratch because that template doesn't fit this requirement or can I do some surgery on it and continue? For instance, I don't know if that template has all the OWIN stuff I need or maybe has too much extra junk (bloated Account controller, Entity Framework, etc.) to be easily transformed/maintained. - Yes u can delete the extra stuffs
Q4: Is token authorization overkill here? Is there an easier way to keep unauthorized users from accessing my controller actions that makes more sense given the nature of the project? -- It is not an over kill. It is the right thing to do for your scenario
I am working on my first ASP.Net MVC 4 application and now stuck with one simple use case.
I need to authenticate one single user (Admin) so that he/she can log in to admin area to perform certain tasks. Though the ASP.Net internet project template has Account controller using simple membership but that seems to have much more than what I actually need. For instance, I don't really need the user registration functionality and user roles. My requirements are fairly simple, just to store one single user in database, give him the options to update his info like password, email etc and grant him access to admin area (admin controller and actions).
What I can't figure out is
Are simple membership or other asp.net membership provider my only options for this simple scenario.
If not what other option do I have in order to use [Authorize] to secure admin actions
You can build a custom method to grab the user and their stored role, then evaluate it in your controller. So, for instance:
public ActionResult GetAdminPage()
{
var loggedInUserName = HttpContext.User.Identity.Name;
var user = somesortofproviderlookupmethod(loggedInUserName);
// Assume user is a bool and is true
if (user)
{
return view("AdminPage");
}
}
The only thing I'm not sure of is whether or not HttpContext.User requires membership. Perhaps someone can shed some light. If so, perhaps you could send the username from the view, but then of course you're trusting the client. So how you are doing user authentication would change this answer somewhat.
Personally, I like membership. It's clean, easy, fast and can be scaled nicely if you end up having additional requirements. Doing something like this would be even easier with membership, since then you can actually use Roles.GetRolesForUser(); and only return the admin view if they contain the role you are looking for.
I have a ASP.NET MVC site with a CAS server set up as the authentication type. I also have a separate database with a Users table and a Roles table (with a User being related to one or more roles). A User is only able to log into the system if the Username is both in the User table and on the CAS system. I have this solution working.
My problem is i now need some form of trigger on User.IsAuthenticated so i can track the current User (from my database), without the possibility that i am trying to allow tracking of a User that has logged out. What I've been thinking is i need to add the User to the HttpContext but i am not sure how to trigger the clearing of the User if the CAS session times out or if the User Logs out.
I also wish to have some functionality such as User.IsInRole (again using my database, not ASP.NET) but am not sure how to go about implementing this. I suppose if i can successfully add the User to the HttpContext then a IsInRole method would simply be a User.Roles.Contains(string role) method but how can that then be used if i wish, for example, to use a method with the DataAnnotation [Authorize(role = "ExampleRole")].
I have looked at questions such as How do I create a custom membership provider for ASP.NET MVC 2? but this doesn't work for me (possibly to do with me using the CAS authentication?)
Any guidance or background reading would be appreciated as i'm really not sure where i should even start. I have read up on GenericPrinciple, IPrinciple and IIdentity but I'm struggling to see how i can apply them to my current project.
Ended up with a custom Authorise Attribute that uses the CAS logon to check the user exists in my database. It also checks the roles of that user. I also used a static class to save the current user in the session with a logout method that abandons the session when the user logs out.
I have kind of a two parter for you. This link does a really good job of explaining how to replace the HttpContext User with your own object: http://bradygaster.com/custom-authentication-with-mvc-3.0
His approach uses MVC filters, but you can also catch the Authentication event in the Global.asax file. Using the forms system with your own implementation can be trivial or not depending on what you're doing, but it boils down to calling FormsAuthentication.SetAuthCookie and .SignOut, amidst your own logic.
public static void FormsLogin(this User user, bool persist)
{
FormsAuthentication.SetAuthCookie(user.DisplayName, persist);
user.AddHistory("Login event.", HistoryType.Login, "SYSTEM");
Users.OnUserLogin(user);
SetLastActivity(user);
}
public static void FormsLogout(this User user)
{
FormsAuthentication.SignOut();
}
Lastly, once you've got the login stuff working out, you can use your own more complex permission system by making a custom Auth Attribute. I remember piecing this together from some other answers and articles but I can't seem to find the sources at the moment, I will try and edit with sources for credit where it's due, if I find them. For now, all I can offer is this gist which offers up one of the attributes I use: https://gist.github.com/1959509
Keep in mind the only really relevant part there is the override of OnAuthorization, which does the actual work.
We have several websites that are set up in the following fashion:
Site1.Web - ASP.NET Web Project (.NET 4.0, WebForms)
Common.Core - Class Library Project (all db interaction)
The web project appears once for each site while the Common.Core project is shared among all sites. We have a login form in the web project that, in order to authenticate, calls into the class library. It would call off a code similar to below:
Common.Core.Authenticate auth = new Common.Core.Authenticate(conStr);
bool validLogin = auth.ValidateUser(userName, password);
if(validLogin)
{
Common.Core.User = auth.GetCurrentUser();
}
The higher ups are pushing for a middle layer service/app tier and want to use a more elegant solution to handle single sign on. Therefore, the decision has been made to use a WIF service to handle the login and validation. Furthermore, we want to try to minimize the code that has to change in each web project, i.e. try to keep as many changes as possible in Common.Core.
I have seen a few samples that show how to add an STS reference to a web project. This would work well in a scenario where the user validation isn't factored into another project like Core.Common. However, in our scenario, how could we handle validation while still going through the common class library?
Ideally, I would like to add an STS reference to the Core.Common class library and replace the direct db logic (auth.ValidateUser above) with a call to an STS service. However, is it even possible to do that? Does the request have to initiate in the web project? If so, is the STS reference required in both places?
Any tutorials or resources which follow the same web project -> class library -> STS service path would be greatly appreciated.
I would also recommend using WIF :-)
In a claims based scenario the authentication process is "reversed". Your app will not call anyone, it will receive the needed information from a trusted source (the STS).
The "STS Reference" is not a library reference. It's a logical connection between your app and the trusted source of security tokens. A token is the artifact your app will use to decide what to do with the user request.
I'd agree with #nzpcmad that it is likely you could entirely remove the calls to you Common.Core library. It might be useful to see what else can you do with it. What does the Common.Core.User object give you?
If it is just properties of a user (e.g. name, e-mail, roles, etc) it is very likely you could just create a new version that simply wraps the IPrincipal supplied byt WIF (a ClaimsPrincipal).
For example (approx. no error handling, pseudo-code):
public User CurrentUser()
{
var user = new User();
var cu = HttpContext.Current.User as IClaimsPrincipal;
user.Name = cu.Name;
user.eMail = (cu.Identity as IClaimsIdentity).Claims.First( c=> c.ClaimType = "eMail" ).Value;
return user;
}
As #nzpcmad says, you can use ADFS or some other STS. Your apps would not care.
One way to achieve this is to FedUtil the ASP.NET project with an instance of ADFS. Essentially, authentication is now "outsourced" and you can simply remove the call to the core library from your app. ADFS is then setup to return whatever attributes the program needs for authorisation as claims. You may need to transform these claims attributes to whatever attributes are passed back to the common core in subsequent calls.
Or you could make the common core "claims aware" in the sense that it now recognizes "claims attributes" as opposed to "common core" attributes. This involves using different .NET classes - no hookup to ADFS is required.
Word of warning - your authentication seems to be all DB related. ADFS cannot authenticate against a DB. It can only authenticate against an instance of AD in the domain that ADFS is installed in (or other AD if trust relationship between AD).
If you want to authenticate against a DB you need a custom STS which is then federated with ADFS. See here: Identity Server.
I already have a User table in my primary application database with an email address (which will act as the user name) and a password. I would like to authenticate using my database instead of the default authentication database (ASPNETDB).
Questions:
Is this a bad idea? Is it a huge can of worms to use my own DB for authentication?
How much work am I adding by doing this? I already have code for hashing the password and a query that will check if the email and password match the DB. So, I wouldn't be starting from scratch.
What would I need to do to use my database instead of ASPNETDB? I'm hoping this can be described in a few simple steps, but if not, could you point me to good source?
Update
I'm still looking for a little more detail here on my third question. Do I need to write my own MembershipProvider? What changes do I need to make to my web.config file? Will the [Authorize] attribute still work if I write my own solution? Can I use the automatically-generated AccountController with some minor modifications or do I basically need to rewrite the account controller from scratch?
It's quite simple, you need to derrive MembershipProvider and implement the ValidateUser method. Take a look at this post. I'm using custom membership provider with Postgres and MVC just fine.
I'll answer your updated questions:
Do I need to write my own MembershipProvider?
If you (a) want to continue using Forms Authentication, and (b) have an authorization table structure that doesn't follow the same conventions as the ASPNETDB, then yes. If you don't need FormsAuth (see below), then you can do away with the MembershipProvider entirely, but I wouldn't recommend it. Or, if you're using the exact same security tables as ASPNETDB but just want to point it to a different database, you can continue using the default provider and simply change its configuration.
What changes do I need to make to my web.config file?
If you are using your own custom MembershipProvider, then you need to register it in the <providers> section of the <membership> element and change the defaultProvider property. If you are using the standard AspNetSqlProvider then you probably just need to change the connection string.
Will the [Authorize] attribute still work if I write my own solution?
Yes, if you stick to Forms Authentication (either use the AspNetSqlProvider or write and register your own membership provider). No, if you abandon Forms Authentication (again, not recommended).
Can I use the automatically-generated AccountController with some minor modifications or do I basically need to rewrite the account controller from scratch?
You should rewrite the AccountController anyway - don't leave demo code in a production app. But if you must - yes, the AccountController will work under the same conditions as above.
No. And I would suspect most people do not trust that cruddy mechanism
Not much at all, especially since you have the table already.
Take a look at this for example: http://forums.asp.net/t/1250726.aspx
Hi ,
Just follow these simple steps :
First, you can delete the .mdf file in App_Data folder. Since we don’t need any of these tables.Then, we need to update the default connection string in the web.config to point to our database.
<connectionStrings>
<add name=”DefaultConnection” connectionString=”Data Source=SERVER\INSTANCENAME;Initial Catalog=DBNAME;Integrated Security=True” providerName=”System.Data.SqlClient” />
</connectionStrings>
Third, Open Nuget Package Manager and write the following commands:
Enable-Migrations
Add-Migration Init
Update-Database
Check out your database, all ASP.NET membership tables with Prefix Asp have been create and then you can test it out by running your application and execute membership actions such as Signing up or Signing in to your application.
Created tables after running above commands:
AspNetRoles
AspNetUserClaims
AspNetUserLogins
AspNetUserRoles
AspNetUsers
__MigrationHistory
Source : https://blogs.msmvps.com/marafa/2014/06/13/how-to-create-asp-net-mvc-authentication-tables-in-an-existing-database/
We're doing exactly this in one of our applications, and find it quite simple. We have an authentication service (called from the controller) that handles the mechanics of hashing the entered password to see if it is a match, then simply returns a bool for a method we call "IsValidLogon".
In our case, the purpose was to keep the management of what should be a pretty trivial task as lightweight as possible.
We bascially ignored ASPNETDB entirely. If we get a valid response from our user/password check, we simply call the standard FormsAuthentication.RedirectFromLoginPage(username, createCookieBool);
Hope that helps.
just building the same, so answer to 1 must be NO :)
I'm using the standard asp.net forms authentication, where i use the FormsAuthentication.RedirectFromLoginPage(username, createCookieBool) method to log a user in.
I gave a user a unique guid (you can use any other user id) and i'm storing it in the UserName parameter along with the username (to display on the masterpage: Html.Encode(Page.User.Identity.Name.Split("|".ToCharArray())[1]))
In each controller/method in which i must know which user is logged on (via User.Identity.Name, split the string and get the userguid).
Also i decorate those routines with the [Authorize] attribute.