Implement ADFS with asp.net roles - c#

I have been working on a project where I have a simple web page integrated with AD FS. The authentication and website are working as expected. I am using VS 2015. My goal is to limit what users can access at the site, "roles" from what I have read and researched. If the logged on user is an admin, grant full access, but if logged on as a regular user limit what pages are available.
Here is the scenario, go to my project URL which is redirected to AD FS sign on, after successful sign on you are at my website. Not much to it.
I have read so much online about different ways to achieve my goal that I am unsure which course is best or simplest to configure. What are my best options here? Keep in mind I have never developed in asp or any other code for that matter. Any help would be appreciated.

There is policy based authorization that is probably the current best practice, however it sounds like role based authorization may be sufficient for you.
To perform role based authorization you'll first need to setup a claim rule in your ADFS for the Relying Party Trust of your application that sends the Role claim type "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". The claim rule would look like this,
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> add(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"), query = ";tokenGroups;{0}", param = c.Value);
Then when your roles arrive at your application in these claims, you'll process them with Windows Identity Foundation (WIF), which is integrated into .NET Framework 4.5+. I believe referencing System.Security.Claims is sufficient to get WIF in your project for processing roles. This "processing" however is done for you by WIF.
At this point you should be able to simply decorate controllers and methods like the following to perform role based authorization, with these Roles equating to the names of groups you are a member of in Active Directory.
[Authorize(Roles = "Administrators")]
public class AdminController : Controller
{
}

Just for interest, there are other ways to do this.
Once you have the roles, you can use IsInRole(role).
You can also use the web.config e.g.
<location path="Page.aspx">
<system.web>
<authorization>
<allow roles="Admin, OtherAdmin" />
<deny users="*" />
</authorization>
</system.web>

Related

Is this the correct way for ASP.NET web application to do authentication?

ASP.NET Web Forms applications.
I checked our company's legacy code, the way they do login is like this:
when user is validated against database with (username, password), they set a session:
Session["authenticated"] = "true";
Every page other than login.aspx is inherited from a class named SecurePage. In SecurePage's OnInit() method, it checks
if (Session["authenticated"] != null)
if true, means authenticated, otherwise means not. So basically the way to do authentication is to see if there is a session named authenticated.
This seems the most crude and intuitive way of doing authentication... I want to ask: is this safe?
Another thing I feel strange is that in the web.config, they have this:
<authentication mode="Windows" />
Shouldn't it be
<authentication mode="Forms" />
since these are web applications? Users credentials are stored in database and these users are outside clients (not internal users).
A slight different version also does this after user is validated against database:
FormsAuthentication.SetAuthCookie( username, true );
what does this do? Besides this statement in login.aspx, I don't see any other pages have any code related to auth cookie. Do we need to set auth cookie by ourselves in code or does .NET framework handle this for us already?
Still another version have the following:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(login, false, 60);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(cookie);
what does this do? Do we need to set this cookie by ourselves in code? or does .NET framework handle this for us already?
These web appliations were developed a long time ago, though I am not sure when. I suspect it is .NET 1.0 era? From my understanding since .NET 2.0,
it has this ASP.NET membership thing, and we can just use <authentication> and <authorization> tags in web.config (and subfolder web.config) to achieve the goal of authentication and authorization. Isn't it? Can anyone give me a history of ASP.NET framework authentiation mechanism? (membership -> simple memebership -> Identity?)
<authentication mode="Windows" />
Above should be used mostly in intranet website, because it's like saying use the computer(windows pc) authentication to access the resource. However, this will never work, since inherited class has a method to validate session key value for a login.
You should make sure that the code redirects user to login page in case the session key is not found. That means, in the else section of below code, you should take users to login page to try again. Which I am sure is happening.
if (Session["authenticated"] != null)
{ /*user is authenticated*/ }else{ /*redirect to login*/ }
Its is recommended to use <authentication mode="Forms" /> if the website is accessible over the internet. Other benefit of using this setting is that you can set default and login page.
Finally, FormsAuthenticationTicket is a class with property and values that are used when working with Forms authentication to identify authenticated users.
Read through msdn article to know more about asp.net membership.
https://msdn.microsoft.com/en-us/library/yh26yfzy%28v=vs.140%29.aspx

FormsAuthentication object obsolete [using MVC5]

I'm using the following code in an MVC5 site:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel loginModel) {
if (ModelState.IsValid) {
var authenticated = FormsAuthentication.Authenticate(loginModel.UserName, loginModel.Password);
if (authenticated) {
FormsAuthentication.SetAuthCookie(loginModel.UserName, true);
return RedirectToAction("AdminPanel");
}
ModelState.AddModelError("", "The username and password combination were incorrect");
}
return View(loginModel);
}
Which throws up the following warning:
System.Web.Security.FormsAuthentication.Authenticate(string, string)'
is obsolete: 'The recommended alternative is to use the Membership
APIs, such as Membership.ValidateUser. For more information, see
http://go.microsoft.com/fwlink/?LinkId=252463.'
First a disclaimer - I'm one of those developers that likes to keep up to date with things, and I prefer to avoid warnings in VS entirely. However, in this particular instance, I am using extremely primitive authentication, which comes directly from the web.config, as follows:
<authentication mode="Forms">
<forms loginUrl="~/account/login" timeout="2880" slidingExpiration="true" name=".ASPXFORMSAUTH">
<credentials passwordFormat="SHA1">
<user name="MyUserName" password="a-big-long-password-hash"/>
</credentials>
</forms>
</authentication>
There is absolutely no further login requirements in this project - using a database is overkill, and there's no need for distributed login; basically, storing the details in web.config is the most ideal solution, and there will probably only be one user per application. I will no doubt abstract the authentication code out from the controller (using DI / IoC), but I am still intending to use the FormsAuthentication object to authenticate against the details in web.config.
While I am fully aware of membership providers, DotNetOpenAuth and the new (but pretty horrible imo) OWIN based authentication model, the above code is more than adequate.
First question - Why has FormsAuthentication been made obsolete when this is a perfectly valid use case? I understand that FormsAuthentication is a sealed black box, is hard to test, and the code behind it is domain specific, but in this particular instance (where I want to read details directly from the section of the web.config), it seems madness to write a membership provider or a custom authentication service?
Second Question - Unless I'm missing something, the new OWIN model is for distributed authentication, and is not suitable for a role based, enterprise level security system. From the many blog posts, white papers and SO posts I've read, it also seems like it's still incomplete. Am I right in my assumptions? Is OWIN the future of all security systems, or should I continue to write bespoke security systems more appropriate for my clients needs?
To your first point, we obsoleted it because it is woefully inadequate by modern security standards. It uses a straight hash with no iteration, which means that anybody who accesses your Web.config can easily figure out what the original passwords were. But if you're willing to accept these risks, you can certainly continue using it going forward. Just suppress the warning and solider on with your original code.
To your second point, there is no relation whatsoever between any of this and OWIN.
Hope this clears it up!
OWIN is not just about security. It is a standard that defines the interfaces between an application framework (ASP.NET MVC) and a web server (IIS). It is a new layer of abstraction Microsoft defined to let .NET developers write applications that are host agnostic, i.e. not dependent on IIS.
OWIN architecture is a pipeline that consists of multiple middleware components. In MVC5, security has been rewritten from scratch as an OWIN middleware component. If this does not work for you and you want to roll your own password verification routine, you can implement your own security middleware. Here is a post describing how to do it.
If you don't want to go that far, this post shows how to rely on the built in authentication framework without the Membership
Everything Levi mentioned is still valid, though. You want to be careful with your own hashing/storage mechanism and pick a good hashing algorithm.
Here some more background info on OWIN security midddleware implemented by Microsoft.

Role-based Security with Forms Authentication , use database for users and roles

I'm working on a web login application using forms authentication and role authentication.
I'm not so familiar with the role authentication , I've searched in google for some tutorials but I couldn't understand.
So ,
1.For creating an user with a certain role everything must be done here:
http://i1.asp.net/asp.net/images/pss/module08_06.jpg ( but , I want to store users info and roles in the asp.net sql database )
What I thought so far is to create a database with the followings table columns:
1.ID(int,primary)
2.Username(varchar(10))
3.Password(varchar(10))
4.RoleType(varchar(10)) - Roles are : Admin / User
On login , check if the user&pass are valid if yes then select the roletype from the database for the current user .
If roletype = Admin
Redirect to a certain page
else if roletype = user
Redirect to other page
But I don't think asp.net web page knows that the column RoleType is used for roles and it doesn't make sense , I mean there's no conection between <allow users="Admin"/> and my column for called RoleType
<location path="\Admin\">
<system.web>
<authorization>
<allow users="Admin"/> //this
<deny users="*"/>
</authorization>
So , if anyone wouldn't mind helping me , I would really appreciate.
Thanks
ASP.NET Comes with a builtin tool for creating a User (Membership) and Roles Database which has all the correct schemas you will need.
I'd strongly suggest you start there, instaed of attempting to roll your own.
http://msdn.microsoft.com/en-us/library/x28wfk74.aspx
Once you've done that, it's trivial to use the built in user/role management screens & the built in SqlMembershipProvider & SqlRoleProvider
Edit
After a quick google, ASP.NET seems to have a very good and detailed tutorial on asp.net membership & roles security
http://www.asp.net/web-forms/tutorials/security
You'll want to read that entire section on Membership, Roles & Admin.

How to get current user who's accessing an ASP.NET application?

To get the current logged in user at the system I use this code:
string opl = System.Security.Principal.WindowsIdentity.GetCurrent().Name.ToString();
I work on an ASP.NET application where I need this information. So I've put my application on a server and tried the code above, and I get "Network Service" in the string opl. I need to know the current user of the PC who accesses my ASP.NET application.
The quick answer is User = System.Web.HttpContext.Current.User
Ensure your web.config has the following authentication element.
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
Further Reading: Recipe: Enabling Windows Authentication within an Intranet ASP.NET Web application
Using System.Web.HttpContext.Current.User.Identity.Name should work.
Please check the IIS Site settings on the server that is hosting your site by doing the following:
Go to IIS → Sites → Your Site → Authentication
Now check that Anonymous Access is Disabled & Windows Authentication is Enabled.
Now System.Web.HttpContext.Current.User.Identity.Name should return something like this:
domain\username
If you're using membership you can do: Membership.GetUser()
Your code is returning the Windows account which is assigned with ASP.NET.
Additional Info Edit:
You will want to include System.Web.Security
using System.Web.Security
The best practice is to check the Identity.IsAuthenticated Property first and then get the usr.UserName like this:
string userName = string.Empty;
if (System.Web.HttpContext.Current != null &&
System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
{
System.Web.Security.MembershipUser usr = Membership.GetUser();
if (usr != null)
{
userName = usr.UserName;
}
}
You can simply use a property of the page. And the interesting thing is that you can access that property anywhere in your code.
Use this:
HttpContext.Current.User.Identity.Name
Don't look too far.
If you develop with ASP.NET MVC, you simply have the user as a property of the Controller class. So in case you get lost in some models looking for the current user, try to step back and to get the relevant information in the controller.
In the controller, just use:
using Microsoft.AspNet.Identity;
...
var userId = User.Identity.GetUserId();
...
with userId as a string.
The general consensus answer above seems to have have a compatibility issue with CORS support. In order to use the HttpContext.Current.User.Identity.Name attribute you must disable anonymous authentication in order to force Windows authentication to provide the authenticated user information. Unfortunately, I believe you must have anonymous authentication enabled in order to process the pre-flight OPTIONS request in a CORS scenario.
You can get around this by leaving anonymous authentication enabled and using the HttpContext.Current.Request.LogonUserIdentity attribute instead. This will return the authenticated user information (assuming you are in an intranet scenario) even with anonymous authentication enabled. The attribute returns a WindowsUser data structure and both are defined in the System.Web namespace
using System.Web;
WindowsIdentity user;
user = HttpContext.Current.Request.LogonUserIdentity;
I ran in the same issue.
This is what worked for me:
Setting up Properties of Windows Authentication in IIS
NTLM has to be the topmost.
Further Web.config modifications, make sure you already have or add if these do not exist:
<system.web>
<authentication mode="Windows" />
<identity impersonate="true"/>
</system.web>
<!-- you need the following lines of code to bypass errors, concerning type of Application Pool (integrated pipeline or classic) -->
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
</system.webServer>
See below a legit explanation for the two nodes and
Difference between <system.web> and <system.webServer>?
And, of course , you get the username by
//I am using the following to get the index of the separator "\\" and remove the Domain name from the string
int indexOfSlashChar = HttpContext.Current.User.Identity.Name.IndexOf("\\");
loggedInWindowsUserName = HttpContext.Current.User.Identity.Name.Substring(indexOfSlashChar + 1);

How do you pass an authenticated session between app domains

Lets say that you have websites www.xyz.com and www.abc.com.
Lets say that a user goes to www.abc.com and they get authenticated through the normal ASP .NET membership provider.
Then, from that site, they get sent to (redirection, linked, whatever works) site www.xyz.com, and the intent of site www.abc.com was to pass that user to the other site as the status of isAuthenticated, so that the site www.xyz.com does not ask for the credentials of said user again.
What would be needed for this to work? I have some constraints on this though, the user databases are completely separate, it is not internal to an organization, in all regards, it is like passing from stackoverflow.com to google as authenticated, it is that separate in nature. A link to a relevant article will suffice.
Try using FormAuthentication by setting the web.config authentication section like so:
<authentication mode="Forms">
<forms name=".ASPXAUTH" requireSSL="true"
protection="All"
enableCrossAppRedirects="true" />
</authentication>
Generate a machine key. Example: Easiest way to generate MachineKey – Tips and tricks: ASP.NET, IIS ...
When posting to the other application the authentication ticket is passed as a hidden field. While reading the post from the first app, the second app will read the encrypted ticket and authenticate the user. Here's an example of the page that passes that posts the field:
.aspx:
<form id="form1" runat="server">
<div>
<p><asp:Button ID="btnTransfer" runat="server" Text="Go" PostBackUrl="http://otherapp/" /></p>
<input id="hdnStreetCred" runat="server" type="hidden" />
</div>
</form>
code-behind:
protected void Page_Load(object sender, EventArgs e)
{
FormsIdentity cIdentity = Page.User.Identity as FormsIdentity;
if (cIdentity != null)
{
this.hdnStreetCred.ID = FormsAuthentication.FormsCookieName;
this.hdnStreetCred.Value = FormsAuthentication.Encrypt(((FormsIdentity)User.Identity).Ticket);
}
}
Also see the cross app form authentication section in Chapter 5 of this book from Wrox. It recommends answers like the ones above in addition to providing a homebrew SSO solution.
If you are using the built in membership system you can do cross sub-domain authentication with forms auth by using some like this in each web.config.
<authentication mode="Forms">
<forms name=".ASPXAUTH" loginUrl="~/Login.aspx" path="/"
protection="All"
domain="datasharp.co.uk"
enableCrossAppRedirects="true" />
</authentication>
Make sure that name, path, protection and domain are the same in all web.configs. If the sites are on different machines you will also need to ensure that the machineKey and validation and encryption keys are the same.
If you store user sessions in the database, you could simply check the existance of the Guid in the session table, if it exists, then the user already authenticated on the other domain. For this to work, you would have to included the session guid in the URL when you redirect the user over to the other website.
Not sure what you'd use for .NET but ordinarily I'd use memcached in a LAMP stack.
The resolution depends on the type of application and environment in which it is running. E.g. on intranet with NT Domain you can use NTLM to pass windows credentials directly to servers in intranet perimeter without any need to duplicate sessions.
The approach how to do this is generally named single sign-on (see Wikipedia).
There are multiple approaches to this problem, which is described as "Cross-domain Single Sign On". The wikipedia article pointed to by Matej is particularly helpful if you're looking for an open source solution - however - in a windows environment I belive you're best off with one of 2 approaches:
Buy a commercial SSO product (like SiteMinder or PingIdentity)
Use MicroSoft's cross-domain SSO solution, called ADFS - Active Direcctory Federation Services. (federation is the term for coordinating the behavior of multiple domains)
I have used SiteMinder and it works well, but it's expensive. If you're in an all MicroSoft environment I think ADFS is your best bet. Start with this ADFS whitepaper.
I would user something like CAS:
[1]: http://www.ja-sig.org/products/cas/ CAS
This is a solved problem and wouldn't recommend rolling your own.
Alternatively if you want to roll your own and the sites in question are not on the same servers or don't have access to a shared database (in which case see the above responses) then you could place a web beacon on each of the sites which would refer back to the other site.
Place a single pixel image (web beacon) on site A which would call site B passing through the users ID (encrypted & time stamped). This would then create a new user session on site B for the user which would be set as logged in. Then when the user visited site B they would already be logged in.
To minimise calls you could only place the web beacon on the home page and or log in confirmation pages. I've used this successfully in the past to pass information between partner sites.

Categories