Develop a custom authentication and authorization

2019-01-15 04:57发布

问题:

I am creating a new ASP.NET MVC 4 application (actually my first MVC application) that is a part of my previous ASP.NET web forms application. I have never used ASP.NET inbuilt authentication methods in any of my project. This new MVC 4 app will be published on a sub-domain of previous app. Login will be done from previous app. A return url should be provided from MVC app to return back to current page if not logged in. However, New User Registration, Account Recovery options are already developed in previous web forms application and I don't want to replicate them in my new MVC application.

A cookie token with token number will be issued from web form application on the event of successful login which will be shared to all domain like *.maindomain.com.

Now I want to merge my own token validation method with ASP.NET inbuilt methods so that I can make use of Authorize and other security related options in my new MVC application.

In my previous application I have developed my custom user validation system in following way.

First, I have following related SQL Server tables

and following classes

public class Token
{
    public static uint GenerateToken(string userEmail, string password, bool isPersistent)
    {
        // this static function generates a uint type unique token number
        // and put this in the cookie "token" using HttpContext.Current.Response object.
        // if isPersistent is set to true then cookie will be persistent otherwise not
        // if there is any problem in creating token then it will throw an Exception with proper message
        // Possible causes of not generating a token are
        // 1. Invalid useremail or password
        // 2. 'State' value in 'Member' table is 'EmailPending' or 'Suspended' (there is an enum for MemberState
    }

    public Token(uint tokenNo, bool validateImmediately = false)
    {
        // simply load token details with a few filed from member table from database
        // Call validate function if validateImmediately is set to true
        // Throws an exception if token does not exists in the database
    }

    public void Validate()
    {
        // Checks for everything like MemberState is Active and Token status is also Active and throws exception if anything wrong
        // and then check (LastAccessedOn.AddSeconds(TokenLife) < AppSettings.Now) is not true
        // Call UpdateStatus function with new token status and current page from HttpContext in comment parameter
    }

    public void UpdateStatus((TokenStatus newStatus, string comment = "")
    {
        // simply write both newStatus and Comment in Token table
        // and remove the token cookie if newStatus is not set to Active
    }

    public uint TokenNumber { get; private set; }
    public uint MemberNumber { get; private set; } // from Member table
    public string Name { get; private set; } // from Member table
    public MemberState MemberState { get; private set; } // from Member table
    public string MemberEmail { get; private set; } // from member table
    public uint BusinsessNo { get; private set; } // from Business table
    public DateTime CreatedOn { get; private set; }
    public DateTime LastAccessedOn { get; private set; }
    public uint TokenLife { get; private set; } // from member
    public string CreatedIP { get; private set; }
    public string LastIP { get; private set; }
    public bool IsPersistent { get; private set; }
    public TokenStatus Status { get; private set; }
    public string Comment { get; private set; }
    public static Token Current
    {
        get
        {
            if (_t == null)
                _t = new Token(uint.Parse(HttpContext.Current.Request.Cookies["token"].Value));
            return _t;
        }
    }
    private static Token _t;
}

public class Member
{
     // all member related operations like new member, send verification email and verify email
}

For logging out user I simply call UpdateStatus like (TokenSatus.Closed, "User logged out"). This method will take care of cookie removal.

Note: Member class has a property bool IsAdmin. You know why its for.

Please suggest me a best solution to develop authentication system according to my needs in MVC application. I am telling you again that options like New User, Account Recovery and Email Verification will be done in my previous ASP.NET web forms application. All I need to just put my Validate() method of Token class on right place in MVC application. I am really confused with several solution available on internet.

回答1:

If you hand-roll your own authentication, the security can only be the as strong as how you store Ticket in client side cookie securely.

Normally, you want to encrypt the auth ticket/token and access via SSL. As long as you store the cookie securely at client side, it should not be an issue.

I also would like to suggest to take a look at how ASP.Net creates Form Authentication Ticket.

Note: If you use ASP.Net Form Authentication Ticket you do not need to store ticket/token in database, because user will send the auth ticket to server on every page request.

var now = DateTime.UtcNow.ToLocalTime();

var ticket = new FormsAuthenticationTicket(
                1, /*version*/
                MemberID,
                now,
                now.Add(FormsAuthentication.Timeout),
                createPersistentCookie,
                TokenID, /*custom data*/
                FormsAuthentication.FormsCookiePath);

var encryptedTicket = FormsAuthentication.Encrypt(ticket);

var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
   HttpOnly = true,
   Secure = FormsAuthentication.RequireSSL,
   Path = FormsAuthentication.FormsCookiePath
};

if (ticket.IsPersistent)
{
   cookie.Expires = ticket.Expiration;
}
if (FormsAuthentication.CookieDomain != null)
{
   cookie.Domain = FormsAuthentication.CookieDomain;
}

_httpContext.Response.Cookies.Add(cookie);

How to create Principal Object

Once authenticated user is requested a page, you need to retrieve auth ticket from cookie, and create a Principal object.

// In Global.asax.cs
void Application_AuthenticateRequest(object sender, EventArgs e)
{
   HttpCookie decryptedCookie = 
      Context.Request.Cookies[FormsAuthentication.FormsCookieName];

   FormsAuthenticationTicket ticket = 
      FormsAuthentication.Decrypt(decryptedCookie.Value);

   var identity = new GenericIdentity(ticket.Name);
   var principal = new GenericPrincipal(identity, null);

   HttpContext.Current.User = principal;
   Thread.CurrentPrincipal =HttpContext.Current.User;
}

// In action method, how to check whether user is logged in 
if (User.Identity.IsAuthenticated)
{

}

Do I need to extend cookie expiration?

If you leave slidingExpiration as true (which is true by default), it will increase the expiration time automatically. (Read more on article)



回答2:

In a high-level concise overview you can do the following:

If you want to proceed with what you've got than the best solution is writing a custom HTTP module which handles the authentication for your MVC web app. Most authentication modules (Windows, Forms, etc) are done through HTTP modules. Here you can read the request and corresponding cookies and set the principal object of the current request and thread. Also if a HTTP 401 occurs - because a unauthenticated user is requesting a secured resource of your mvc webapp - you can redirect them to the login page of your other web app and let them redirect the user back to the one the user was originally requesting.

A different road you may take is re-evaluating what you've got. You are basically trying to achieve Single Sign On and federated identity / authentication for your applications. For this I would recommend solutions designed for this. The SAML and WS-Federation protocol are two popular standards that provides this. Both protocols achieve more or less the same goals.

There are a lot of solutions out there which are based on one if these protocols.

One, for example is the Windows Identity Foundation. As of .Net 4.5, this is integrated in the .Net framework. Before that available as a separate download. WIF supports the WS-Federation protocol. Here everything is based on what they call claim based security (see more info on this subject by the links below).

The SAML protocol is not supported by WIF although there exists some old library which is a CTP version. I would not recommend using it.

Going down the road of re-evaluating how you authenticate your users and manage their identities could be a lot of work. So it's up to you to decide if it's worth the investment. I have to finish but here are some more links to the subjects at hand:

  • Federated Identity
  • Introduction to claims
  • Claims based Identity
  • Introduction to claims based security - part 1 and al the other parts.
  • SSO vs federated login

Although it's not a ready to use answer I hope it helps you in finding your answer. Good luck!