9

I have a small C# MVC5 app that I'm building and am ready to add the user security module to it. (previously I just created a session variable for testing roles) However, my security needs do not fit any of the prebuilt security modules that I've seen, i.e. SimpleMembership etc.

Here's a summary of my situation and needs:

  • No passwords -- username/password auth is not allowed

  • Users are authenticated at the web server level using a smartcard with client certificate -- I do not manage the server and will never see their card

  • IIS populates the Request.ClientCertificate and a few others, this is all I have access to

  • My app will have dedicated user accounts -- not using Windows auth etc -- so more like a username/password system without ever entering a username or password

  • The server is authenticating them to access the server by using some form of Windows auth with their smart card certificate, but I can't, I just have to accept the certificate loaded into the Request collection

  • User tables are stored in SQL Server (for now...)

  • Prefer not to use EntityFramework, since all app data comes from an Oracle DB and trying to get approval to move authentication/authorization tables into it and eliminate working from two DBs, and EF for Oracle doesn't work in our environment, so I'm using OleDb instead :(

What is the best way to go about implementing a scheme like this? What I've started doing is building three tables -- Users, Roles, and UserRoles -- and the Users table holds a copy of the (string) ClientCertificate. Users who come in would be authenticated into the app by pulling the Request.ClientCertificate and looking for a matching Users record, then getting the list of roles from UserRoles. A user object would be stored in the session containing the user and role info, and attributes would be used on controllers to control access by requiring certain roles.

(we have another app that uses this basic approach, but it is J2EE on Linux so I can't just reuse its code)

However, I've also started reading about IIdentity and IPrincipal but I'm not 100% clear on whether or not that is something I can use. Clearly I'd prefer to use a security model designed by experts. So should I build my authentication system using custom classes that inherit from IIdentity and IPrincipal? Or is there some other approach I should use?

It is entirely possible that something like SimpleMembership can be customized to meet my needs, but if so I'm not aware of it.

Thanks for the advice, much appreciated.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
DaveCan
  • 361
  • 1
  • 5
  • 12

2 Answers2

5

You could use Microsoft's Identity for this kind of advanced scenarios as well. Identity so modular you could use any data storage with any schema you want. Password for authentication in Identity is not necessary you could implement your own scenario. consider this simple example:

// imaging this action is called after user authorized by remote server
public ActionResoult Login()
{
    // imaging this method gets authorized certificate string 
    // from Request.ClientCertificate or even a remote server
    var userCer=_certificateManager.GetCertificateString();

    // you have own user manager which returns user by certificate string
    var user=_myUserManager.GetUserByCertificate(userCer);

    if(user!=null)         
    {
        // user is valid, going to authenticate user for my App
        var ident = new ClaimsIdentity(
            new[] 
            {
                // since userCer is unique for each user we could easily
                // use it as a claim. If not use user table ID 
                new Claim("Certificate", userCer),

                // adding following 2 claim just for supporting default antiforgery provider
                new Claim(ClaimTypes.NameIdentifier, userCer),
                new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),

                // an optional claim you could omit this 
                new Claim(ClaimTypes.Name, user.Name),

                // populate assigned user's role form your DB 
                // and add each one as a claim  
                new Claim(ClaimTypes.Role, user.Roles[0].Name),
                new Claim(ClaimTypes.Role, user.Roles[1].Name),
                // and so on
            },
            DefaultAuthenticationTypes.ApplicationCookie);

        // Identity is sign in user based on claim don't matter 
        // how you generated it Identity take care of it
        HttpContext.GetOwinContext().Authentication.SignIn(
            new AuthenticationProperties { IsPersistent = false }, ident);

        // auth is succeed, without needing any password just claim based 
        return RedirectToAction("MyAction"); 
    }
    // invalid certificate  
    ModelState.AddModelError("", "We could not authorize you :(");
    return View();
}

As you can see we authorized user and populated roles without any dependency to username, password and any data storage since we used our own user manager.

Some usage example:

[Authorize]
public ActionResult Foo()
{
}

// since we injected user roles to Identity we could do this as well
[Authorize(Roles="admin")]
public ActionResult Foo()
{
    // since we injected our authentication mechanism to Identity pipeline 
    // we have access current user principal by calling also
    // HttpContext.User
}

This is a simple example you could implement your custom scenario extend IIdenity as well. Read my other similar answers like this and this for more examples to see how you can do almost everything by Claims.

Also you could browse and download Token Based Authentication Sample repo as a simple working example.

Community
  • 1
  • 1
Sam FarajpourGhamari
  • 14,601
  • 4
  • 52
  • 56
  • This is very useful thank you. I've spent the afternoon reading up on IIdentity and IPrincipal and have started rolling my own but had not seen a true end-to-end example like this that would meet my needs. I'm going to dig into this over the next few days and see how it works out but this looks like a good start. – DaveCan Aug 13 '15 at 21:38
  • By the way, to use the Authorize attribute you have to enable forms authentication in web.config correct? I'm assuming a non-authorized user would be automatically redirected to the Login action as specified in the web.config. Also, since I'm not familiar with the .User property how does that persist from one pageview to another? i.e. once I authenticate the user and they click a link to initiate a new HTTP request, how is the authorization persisted? In the session? Is it seamless or do I need to do something else not shown? I want to avoid a DB hit to authenticate for every page. Thanks! – DaveCan Aug 13 '15 at 21:42
  • You don't need to use old from authentication. You just need to implement Identity framework to your App. If you don't know how read [this](http://stackoverflow.com/questions/31960433/adding-asp-net-mvc5-identity-authentication-to-an-existing-project/31963828#31963828) but for your scenario you don't even need install EF. Therefore a slight modification is needed. – Sam FarajpourGhamari Aug 13 '15 at 21:49
  • Since you will configure Identity cookie based. Authorization is persisted you don't need to do anything Identity take care of them. In every part of code you have access current user by calling `HttpContext.Current.User` – Sam FarajpourGhamari Aug 13 '15 at 21:54
  • Thanks. Your other links look great too. I carved out a bit of time today and I've got the claim created and the Authorize attribute checks correctly. However, it defaults to redirect to /Account/Login. I found that in Startup.Auth.cs and changed it to redirect to an ActionResult NotAuthorized() on the Home controller that just displays a generic "not authorized" view with instructions on how to request access according to our policies. Is there a better way to handle that? – DaveCan Aug 14 '15 at 20:04
  • Actually I spoke too soon: the Authorize attribute is NOT allowing the user through, it is always denying access even after I create the ClaimsIdentity as shown above and pass it to the SignIn() method. Stepping through the debugger it appears the .User method on the controller doesn't see the claims even immediately after calling SignIn(). Not sure what I'm doing wrong there but it may be a common mistake that I'm making. I'm not hitting the DB yet, just doing a hard-coded test almost exactly as you showed above. Thanks for any help/advice, much appreciated. – DaveCan Aug 14 '15 at 20:28
  • If you configured Identity right this code must work. I have just tested. You can not see user just after SignIn() method. User must send a new request to server and in the new request you could see authorized user. I think you have not configured Identity or OWIN correctly or you have some problems with cookie. – Sam FarajpourGhamari Aug 14 '15 at 21:52
  • I have added a simple working example in my github acc. See updated post for link. – Sam FarajpourGhamari Aug 15 '15 at 12:46
  • Thank you that is very helpful. After studying it I created a new project from scratch and moved files from my old project to the new manually. I did that to update all nuget packages on a separate machine that is not restricted (my PC cannot contact nuget). Now I get an error when hitting Authentication.SignIn(): "No owin.Environment item was found in the context". The solution is to have a Startup.cs file, but your project doesn't have one. How does your project call the Identity config? How are you avoiding this error? I feel like I'm almost there but this is a stumbling block. Thanks! – DaveCan Aug 18 '15 at 21:46
  • To clarify, I get the error with and without a Startup.cs file. The Startup.cs file just contains a Configuration(IAppBuilder app) method with one line, ConfigureAuth(app). Not sure what I'm missing here.... thanks. – DaveCan Aug 18 '15 at 21:48
  • I think I've got it! I went through your web.config file and found the owin:AppStartup key and added one to my project and now it works! I still have to work out the implementation details within my app but it properly creates an Identity, adds the claims with the certificate name and roles, and locks the controller with the Authorize attribute. So I think I've got enough to go on for now, but I may hit you up with a question or two later if you don't mind. And I'm accepting this answer because it was outstanding, you did fantastic work, THANK YOU! :) – DaveCan Aug 18 '15 at 21:59
  • I am so glad it helps you. Feel free to ask any question. I will be so glad if I could help you. ;) – Sam FarajpourGhamari Aug 19 '15 at 10:09
0

From the sounds of it the user is already authenticated, as such in your controller you can simply access the User property on Controller base class that your controllers inherit. This is property returns an IIPrincipal which has a property called Identity which returns an IIdentity.

So for example if you wanted to retrieve the currently logged in user's username in your controller you would access User.Identity.Name or if you wanted to ensure that the user was logged in you could check User.Identity.IsAuthenticated.

You absolutely should not be pulling anything out of the client certificates, essentially the server should have already authenticated the user, you don't care that it was done via a certificate rather than a username/password.

Ben Robinson
  • 21,601
  • 5
  • 62
  • 79
  • Well the server authenticated the user, but it is a shared server where my app is just one of several, and the user has *not* been authenticated into my app yet. Not sure if that makes sense. So I don't have a controller property available that I'm aware of. Plus I'm developing using the local server in VS2013 so it doesn't pull the certificate when I am developing, so I'll have to shim in some certificate strings that I can use to look up various users. i.e. plug in some "CN=..." strings for dev purposes, but pull the "CN=..." from Request.ClientCertificate in prod. Make sense? – DaveCan Aug 13 '15 at 16:04
  • The controller property is there whether the user is authenticated or not. If the user is authenticated "on the server" then they should appear as authenticated to your app I believe, you need to test and find out for sure. This kind of authentication (it's windows auth basically) happens entirely outside your app. What you need to do in your app is authorisation, i.e. apply roles and determine what the user can and can't do. You absolutely should not be doing anything with the certificate. – Ben Robinson Aug 14 '15 at 07:58