jevannie
jevannie

Reputation: 396

Authentification and Authorization in ASP.NET MVC 5

I am very confused with Authentication and Authorization in ASP.NET MVC 5.

I am working on an existing website and I need to add security in it. By security I mean Authentication (Logins) and Authorization (Roles). I have access to a Webservice, but not directly to the database though I can access the Entities (Users, Roles etc.).

Membership Provider seems to be a bit old, so I took a look at Identity but it looks complicated to implement to an existing project, especially when I don't have direct access to the database.

What would be a good solution ? What are the best practices ? Could you suggest me any good resource so I can suits my needs ?

Thank you.

Upvotes: 1

Views: 2458

Answers (1)

jevannie
jevannie

Reputation: 396

In case someone feel as lost as I was, here is a potential solution using Claims. Ath the end, you will know how to handle Authentication, Authorization and Roles. Hope this can help.

Startup config

In the root folder off my project I have created a file, startup.cs. She contains a partial class that we will use to configure the application to use a cookie that store the signed user.

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
    }
}

Then, in the App_Start I have a file, Startup.Auth.cs

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login")                
        });
    }
}

Controller

First, I have created an AcountController.cs with attribute of type IAuthenticationManager. This attribute gets the authentication middleware functionality available on the current request.

public class CompteController : Controller
{ 
    private IAuthenticationManager AuthenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }
}

Then, I have a classic view called Login with GET and POST. In the post I check in my Webservice if the user can Log In. If he can, I call a the magic function to authenticate. In this code, the class User is the custom User I get in the Webservice. He don't implement IUser.

private void AuthentifyUser(User user, bool isPersistent)
{  
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);

    CustomIdentity identity = new CustomIdentity(user);
    CustomPrincipal principal = new CustomPrincipal(identity);
    Thread.CurrentPrincipal = principal;

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

Last important method in my Controller allow users to Log Out.

public ActionResult Deconnexion()
{
    AuthenticationManager.SignOut();
    return RedirectToAction("Login", "Account");
}

Claims

CustomIdentity and CustomPrincipal are two custom class that I use for the Claims system. They indirectly implement IIdentity and IPrincipal. I put them in a separate new folder.

-Remember, A principal object represents the security context of the user on whose behalf the code is running, including that user's identity (IIdentity) and any roles to which they belong.

-An identity object represents the user on whose behalf the code is running.

public class HosteamIdentity : ClaimsIdentity
{
    public HosteamIdentity(User user)
        : base(DefaultAuthenticationTypes.ApplicationCookie)
    {
        AddClaim(new Claim("IdUser", user.Id.ToString()));           
        AddClaim(new Claim(ClaimTypes.Name, user.Name));
        AddClaim(new Claim(ClaimTypes.Role, user.Role));
    }

    public int IdUser 
    { 
        get
        {
            return Convert.ToInt32(FindFirst("IdUser").Value);
        }
    }

    //Other Getters to facilitate acces to the Claims.
}

The Principal gives us access to the Identity.

public class HosteamPrincipal : ClaimsPrincipal
{
    private readonly HosteamIdentity _identity;
    public new HosteamIdentity Identity
    {
        get { return _identity; }
    }


    public HosteamPrincipal(HosteamIdentity identity)
    {
        _identity = identity;
    }  

    public override bool IsInRole(string role)
    {
        return _identity.Role == role;
    }
}

Access the CustomPrincipal

Now, I will lgo to the gGlobal.asax, here we will override the Application_PostAuthenticateRequest event. This event is fired when a security module has established the identity of the user.

We will use Thread.CurrentPrincipal, this static object Gets or sets the thread's current principal (for role-based security), so it is perfectly adapted to our case !

You may have to adapt the code here. I personally have to request my Webservice, this may not be your case.

Just talking briefly about our constructors. The fist is empty, we will use it when we don't care about Roles

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
           Thread.CurrentPrincipal = new HosteamPrincipal(
                new HosteamIdentity(
                    WebService.GetUser(
                        HttpContext.Current.User.Identity.Name)));            
        }
    }

In most case, retrieving the user by is Name is not a good practice. Please, adapt the above code to your solution.

Authorize Attribute Filter

Now, it will be great if we could easily tell which Controller or Action can be accessed by an authenticated user. To do so, we will use Filters.

Filters are custom classes that provide both a declarative and programmatic means to add pre-action and post-action behavior to controller action methods. We use them as annotation, for example [Authorize] is a Filter.

As there is to many things to explain, I will let you read the comments, they are very explicit.

Just talking briefly about our Constructors. -The first one is empty, we will use it when we don't care about Roles. We access it by writing the annotation [CustomAuthorize] abose a Controller or an Action. -The second one, takes an array of Roles, we will use it by writing the annotation [CustomAuthorize("Role1", "Role2", etc.)] abose a Controller or an Action. He will define which Roles access the Controller or action

public class CustomAuthorize : AuthorizeAttribute
{
    private new string[] Roles { get; set; }


    public CustomAuthorize() { }
    public CustomAuthorize(params string[] roles)
    {
        this.Roles = roles[0].Split(',');
    }


    /// <summary>
    /// Check for Authorizations (Authenticated, Roles etc.)
    /// </summary>
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext.Request.IsAuthenticated)
            if (Roles != null)
            {
                foreach (string role in Roles)
                    if (((HosteamPrincipal)Thread.CurrentPrincipal).IsInRole(role))
                        return true;
                return false;            
            }
            else                    
                return true;
        return false;
    }


    /// <summary>
    /// Defines actions to do when Authorizations are given or declined
    /// </summary>
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (!AuthorizeCore(filterContext.HttpContext))
            HandleUnauthorizedRequest(filterContext);
    }


    /// <summary>
    /// Manage when an Authorization is declined
    /// </summary>
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
            filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}

Upvotes: 2

Related Questions