12

I have a webforms website that is calling into a new MVC6 website that we are working on. The user will login as they always have done on the webforms website using forms authentication and then get redirected to the new MVC6 website. I know in MVC6 that I should be using Cookie Authentication but cannot get it to decrypt the cookie. I suspect its down to changes around web.config and machinekey but am really stuck.

Here is what I have done.

I have set up cookie authentication as follows

        app.UseCookieAuthentication(options =>
        {
            options.CookieName = "MyWebformsCookie";
            options.AutomaticAuthenticate = true;
            options.AuthenticationScheme = "Cookies";
            options.TicketDataFormat = new MySecureDataFormat();
            options.DataProtectionProvider = new MyDataProtectionProvider();
            //options.CookieDomain = "localhost";
        });

The class is as follows

public class MySecureDataFormat : ISecureDataFormat<AuthenticationTicket>
{
    public string Protect(AuthenticationTicket data)
    {
        return string.Empty;
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        return string.Empty;
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        return null;
    }

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var ticket = FormsAuthentication.Decrypt(protectedText);
        return null;
    }
}

The cookie is being read, and the Unprotect method called, but then it errors on the FormsAuthentication.Decrypt method with error

An exception of type 'System.Web.HttpException' occurred in System.Web.dll but was not handled in user code

Additional information: Unable to validate data.

Stack = at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Boolean useValidationSymAlgo, Boolean useLegacyMode, IVType ivType, Boolean signData) at System.Web.Security.FormsAuthentication.Decrypt(String encryptedTicket) at WebApplication.Mvc.MySecureDataFormat.Unprotect(String protectedText, String purpose) in C:\SVNCode\GlobalConnectV2\WebApplication.Mvc\Startup.cs:line 153
at Microsoft.AspNet.Authentication.Cookies.CookieAuthenticationHandler.d__9.MoveNext()

So this leads me to believe that its not reading machine key. I have this in the web.config in wwwroot folder

<configuration>
  <system.webServer>
    ...
  </system.webServer>
  <system.web>
    <machineKey compatibilityMode="Framework20SP2" validation="SHA1" decryption="AES" validationKey="mykey" decryptionKey="dec" />
  </system.web>
</configuration>

This works on earlier MVC apps but guessing something changed in MVC6. I have also tried the following but no luck

    services.ConfigureDataProtection(configure =>
    {
        configure.UseCryptographicAlgorithms(new Microsoft.AspNet.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptionOptions()
        {
            EncryptionAlgorithm = Microsoft.AspNet.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_CBC,
            ValidationAlgorithm = Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA256
        });

    });

Any advice?

Maxime Rouiller
  • 13,614
  • 9
  • 57
  • 107
Punchmeister
  • 183
  • 2
  • 9

3 Answers3

15

I had no joy attempting to use FormsAuthentication.Decrypt() in an ASP.NET 5 application.

In the end I wrote a decryption routine, based on the documentation available, and also looking at reference source code that Microsoft made available for system web.

The classes required to decrypt a forms authentication cookie that uses SHA1 for validation, and AES for encryption, can be found on my GIST here: https://gist.github.com/dazinator/0cdb8e1fbf81d3ed5d44

Once you have these, you can create a custom TicketFormat as before:

public class FormsAuthCookieTicketFormat : ISecureDataFormat<AuthenticationTicket>
{

    private LegacyFormsAuthenticationTicketEncryptor _Encryptor;
    private Sha1HashProvider _HashProvider;

    public FormsAuthCookieTicketFormat(string decryptionKey, string validationKey)
    {
        _Encryptor = new LegacyFormsAuthenticationTicketEncryptor(decryptionKey);
        _HashProvider = new Sha1HashProvider(validationKey);
    }

    public string Protect(AuthenticationTicket data)
    {
        throw new NotImplementedException();            
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        throw new NotImplementedException();
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        throw new NotImplementedException();
    }

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var ticket = _Encryptor.DecryptCookie(protectedText, _HashProvider);

        var identity = new ClaimsIdentity("MyCookie");
        identity.AddClaim(new Claim(ClaimTypes.Name, ticket.Name));
        identity.AddClaim(new Claim(ClaimTypes.IsPersistent, ticket.IsPersistent.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Expired, ticket.Expired.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Expiration, ticket.Expiration.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.CookiePath, ticket.CookiePath));
        identity.AddClaim(new Claim(ClaimTypes.Version, ticket.Version.ToString()));           

        // Add some additional properties to the authentication ticket.
        var props = new AuthenticationProperties();
        props.ExpiresUtc = ticket.Expiration.ToUniversalTime();
        props.IsPersistent = ticket.IsPersistent;

        var principal = new ClaimsPrincipal(identity);

        var authTicket = new AuthenticationTicket(principal, props, CookieDetails.AuthenticationScheme);
        return authTicket;
    }

And wire it up like so:

var formsCookieFormat = new FormsAuthCookieTicketFormat(_DecryptionKeyText, _ValidationKeyText);

        app.UseCookieAuthentication(options =>
        {
            // shortened for brevity... 
            options.TicketDataFormat = formsCookieFormat ;
            options.CookieName = "MyCookie";                
        });
Darrell
  • 1,905
  • 23
  • 31
  • 1
    Thank you for your solution, It made my day easy. Is it ok that I can use your source code in my projects? Thank you so much in advance. – Anil Jun 13 '16 at 13:00
  • Decryption part of code is awesome and working well. I am trying to use the Encryption part of your code, Having some problem with signature validation. Is it working well on your side? Do you have any working examples to share – Anil Jul 18 '16 at 14:20
  • Encryption and Decryption of FormsAuth Cookie works perfectly now in AspDotNetCore +1 @Darrell – Anil Aug 30 '16 at 06:43
  • 1
    Thanks @Anil - for anyone else looking - the code and unit tests can be found here: https://github.com/dazinator/AspNetCore.LegacyAuthCookieCompat – Darrell Aug 31 '16 at 12:12
  • Please help. I am trying Single Sign On between .Net Framework and .Net Core projects. In my .Net Framework project I have used System.Web.Security.FormsAuthentication to Encrypt and saved to cookie. Now I am trying your package LegacyAuthCookieCompat to Decrypt. But I am getting Signature Verification Failed error. Can you tell how to make it work? – Gautam Jain Oct 27 '18 at 07:21
  • 1
    Fixed it. I had compatibilityMode="Framework45" in my .Net Framework project. It worked after removing this setting. – Gautam Jain Oct 27 '18 at 07:40
2

There is a whole section of documentation on docs.asp.net that is named Replacing machineKey.

It mostly has to do with the package Microsoft.AspNet.DataProtection.SystemWeb.

You will then be able to setup your key either in code or in config and be able to read those cookies directly from .

Here's a code sample (from the documentation) on how to do it:

public override void ConfigureServices(IServiceCollection services)
{
    services.ConfigureDataProtection(configure =>
    {
        configure.SetApplicationName("my-app");
        configure.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\myapp-keys\"));
        configure.ProtectKeysWithDpapi();
    });
}

Of course you have to add the previously mentioned package in your list of dependencies and that may force you to use Windows (not sure but it does compile in CoreClr).

This will NOT work with existing machineKey but it will generate a new set that will allow you to start from there.

Maxime Rouiller
  • 13,614
  • 9
  • 57
  • 107
  • 2
    The question was specifically relating to an MVC 6 (ASP.NET 5) website, and as such there is no web.config file. The documentation you linked doesn't explain how to use the DataProtection library (i.e through code, or via config) in that scenario. – Darrell Dec 22 '15 at 19:09
  • It does. Added the code snippet from the documentation page and added more details. – Maxime Rouiller Dec 22 '15 at 19:18
  • 2
    No.. the Microsoft.AspNet.DataProtection.SystemWeb package is specifically for ASP.NET 4.5 (.net 4.5.1).The documentation you have linked to relating to it is specifically for ASP.NET 4.5. Read it - that whole section is actually named "replacing machine key in ASP.NET 4.5.1" - scroll down and read the comments below it and you will see me clarifying with the Microsoft guys. Read http://docs.asp.net/en/latest/security/data-protection/introduction.html which tells you also that the Microsoft.AspNet.DataProtection.SystemWeb is for ASP.NET 4.X You can use DataProtection in ASP.NET 5 also but.. – Darrell Dec 24 '15 at 09:55
  • 3
    .. (continued from above) your answer does not explain how to configure it to decrypt an existing cookie that has been encrypted (not using DataProtection) from an ASP.NET web forms site.. You simply copied a snippet showing how to make it use new keys and haven't explained how that solves the problem. – Darrell Dec 24 '15 at 09:58
1

When you authenticate the user, set the authentication cookie's domain to the second-level domain, i.e. parent.com (your asp.net side). Each sub-domain (MVC site) will receive the parent domain's cookies on request, so authentication over each is possible since you will have a shared authentication cookie to work with.

Authentication code:

System.Web.HttpCookie authcookie = System.Web.Security.FormsAuthentication.GetAuthCookie(UserName, False);
authcookie.Domain = "parent.com";
HttpResponse.AppendCookie(authcookie);
HttpResponse.Redirect(System.Web.Security.FormsAuthentication.GetRedirectUrl(UserName, 
                                                                   False));

Refer question Forms Authentication across Sub-Domains

and ASP.NET webforms and MVC authentication sharing via cookie

Community
  • 1
  • 1
Anil
  • 3,722
  • 2
  • 24
  • 49
  • Thanks for quick response, however, I am using a cookie viewing tool and can see cookie domain is of my asp.net site. I have set the cookiedomain to match that on the Cookie Authentication options. The encryped cookie value is being read, it's just I cannot decrypt it – Punchmeister Dec 14 '15 at 11:29
  • Is this related to adding web.config; EnableViewStateMAC=false – Anil Dec 14 '15 at 11:40
  • As you are doing this corretly, This problem can also occurs when the following conditions are true: The HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\fipsalgorithmpolicy registry subkey is set to 1. ASP.NET 2.0 uses the RijndaelManaged implementation of the AES algorithm when it processes view state data. the AES algorithm is not part of the Windows Platform FIPS validated cryptographic algorithms. refer https://support.microsoft.com/en-us/kb/911722 – Anil Dec 14 '15 at 11:48
  • This question is nothing to do with the act of making the same cookie available to both websites, the cookie is accessbile to both, it's just the ASP.NET 5 application cannot use the `FormsAuthentication.Decrypt()` method to decrypt it. None of your links seem to help with this specific scenario! – Darrell Dec 22 '15 at 19:14