I have a number of existing ASP.NET MVC web application that all use ASP.NET Membership and Forms Authentication and not using OWIN, but I would like new apps to be developed in .NET Core 1.1 using IdentityServer4 for auth. I have an IdentityServer implemented with a custom user store so existing users from the old ASP.NET Membership database can log into my IdentityServer (see this StackOverflow question for more information about that), however the authentication cookie created by ASP.NET Identity isn't recognized by the older apps using Forms Authentication.
I'd like to have single sign-on between the older Forms Authentication apps and the newer apps using ASP.NET Identity, preferably with make as few changes as possible to the older apps. Is there a way to make ASP.NET Identity generate authentication cookies that Forms Authentication can understand?
I think in both cases, the contents of the cookie is a serialized and encrypted ClaimsPrincipal so it seems like my big stumbling block is getting both the Forms Authentication app and the ASP.NET Identity app to be on the same page with respect to encrypting/decrypting the cookie. Since Forms Authentication encrypts the cookie using the <machineKey> element in the web.config file and ASP.NET Identity in .NET Core uses DPAPI, I'll have to figure out how to bridge that gap. Maybe something like this?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<IdentityOptions>(options =>
{
...
options.Cookies.ApplicationCookie.DataProtectionProvider =
new FormsAuthenticationDataProtector();
...
});
}
FormsAuthenticationDataProtector.cs
public class FormsAuthenticationDataProtector : IDataProtector
{
public IDataProtector CreateProtector(string purpose)
{
return this;
}
public byte[] Protect(byte[] plaintext)
{
// TODO: Mimic the Forms Authentication encryption algorithm.
}
public byte[] Unprotect(byte[] protectedData)
{
// TODO: Mimic the Forms Authentication decryption algorithm.
}
}
But I have no idea what the bodies of the Protect() and Unprotect() methods should be.
This discussion I had with a couple of ASP.NET Identity and IdentityServer devs on GitHub was really helpful. The short answer to my question about sharing a cookie between Forms Authentication and ASP.NET Identity was "You're insane!" Fortunately, there are some alternatives.
Brock Allen pointed me to IdentityServer's GitHub repo for client examples. For me, I think the MvcFormPostClient example is going to do the trick. It's actually not that hard to implement OpenID Connect in an older MVC app without OWIN or ASP.NET Identity.
Related
I would like to share an authentication cookie provided by the ASP.NET Framework app and use it in ASP.NET Core app. To encrypt cookie I am using Data Protection, which is a default in .NET Core and requires package Microsoft.AspNetCore.DataProtection.SystemWeb for .NET Framework.
When .NET Core generates and protects cookie authentication works for .NET Core app. When .NET Framework generates and protects cookie .NET Core app doesn't use it.
According to https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/replacing-machinekey?view=aspnetcore-3.1 properly protected cookie should start with "CfDJ8", which is the base64 representation of the magic "09 F0 C9 F0" header that identifies a payload protected by the data protection system. The cookie generated by .NET Core starts with "CfDJ8" but cookie generated by .NET Framework starts with "09F0C9F0".
It seems that cookie is correctly generated and protected but .NET Core uses base64 to encode cookie and .NET Framework uses hexadecimal strings.
The question is, how to set up common cookie encoding for an authentication cookie encrypted by ASP.NET Core Data Protection?
I followed the guide here to share cookie between as .NET framework Webforms application and a newer aspnetcore 5 application.
https://learn.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-5.0
In my case, the webforms app authenticates and issues the cookie. I see that the cookie is generated and set correctly with the encrypted header text indicating the Data Protection system is in effect (see the screenshot below).
The webforms app works fine, however the aspnetcore app does not use this cookie! Any action with [Authorize] attribute is taken back to the login page. Is there anything else that needs to be done on the aspnet core side to make this work?
I followed the guide at https://github.com/blowdart/idunno.CookieSharing and it worked perfectly. The guide is far more in depth than this answer. As a summary, you want to use DataProtector in both apps, with Microsoft.Owin.Security.Interop.DataProtectorShim enabling DataProtector to work in the .Net Framework App and Microsoft.Owin.Security.Interop.AspNetTicketDataFormat providing the ticket format for the .Net Framework App :
.NET Core
Use Data Protection (OP is already doing so)
For consistency (and to ensure implementations match), set your TicketDataFormat explicitly
So, something like this:
var protectionProvider = DataProtectionProvider.Create(
new DirectoryInfo("c:\keyring"));
var dataProtector = protectionProvider.CreateProtector(
"CookieAuthenticationMiddleware","Cookie","v2");
var ticketFormat = new TicketDataFormat(dataProtector);
services.ConfigureApplicationCookie(options =>
{
options.TicketDataFormat = ticketFormat;
//...
});
.Net Framework
Use Data Protection
For consistency (and to ensure implementations
match), set your TicketDataFormat explicitly.
Add the Microsoft.Owin.Security.Interop nuget package. You'll need it to wrap your DataProtector in a DataProtectorShim (and explicitly use the AspNetTicketDataFormat interop class).
var cookieOptions = new CookieAuthenticationOptions
{
TicketDataFormat = ticketFormat,
//...
};
var protectionProvider = DataProtectionProvider.Create(
new DirectoryInfo("c:\keyring"));
var dataProtector = protectionProvider.CreateProtector(
"CookieAuthenticationMiddleware", "Cookie", "v2");
var ticketFormat = new AspNetTicketDataFormat(
new DataProtectorShim(dataProtector));
You should use data protection API in classic asp.net. To achieve this you can refer to below steps or direct check this
Document
To share authentication cookies between two different ASP.NET 5 applications, configure each application that should share cookies as follows.
Install the package Microsoft.AspNet.Authentication.Cookies.Shareable into each of your ASP.NET 5 applications.
In Startup.cs, locate the call to UseIdentity, which will generally look like the following.
// Add cookie-based authentication to the request pipeline.
app.UseIdentity();
Remove the call to UseIdentity, replacing it with four separate calls to UseCookieAuthentication. (UseIdentity calls these four methods under the covers.) In the call to UseCookieAuthentication that sets up the application cookie, provide an instance of a DataProtectionProvider that has been initialized to a key storage location.
// Add cookie-based authentication to the request pipeline.
// NOTE: Need to decompose this into its constituent components
// app.UseIdentity();
app.UseCookieAuthentication(null, IdentityOptions.ExternalCookieAuthenticationScheme);
app.UseCookieAuthentication(null, IdentityOptions.TwoFactorRememberMeCookieAuthenticationScheme);
app.UseCookieAuthentication(null, IdentityOptions.TwoFactorUserIdCookieAuthenticationScheme);
app.UseCookieAuthentication(null, IdentityOptions.ApplicationCookieAuthenticationScheme,
dataProtectionProvider: new DataProtectionProvider(
new DirectoryInfo(#"c:\shared-auth-ticket-keys\")));
Caution: When used in this manner, the DirectoryInfo should point to a key storage location specifically set aside for authentication cookies. The application name is ignored (intentionally so, since you’re trying to get multiple applications to share payloads). You should consider configuring the DataProtectionProvider such that keys are encrypted at rest, as in the below example.
app.UseCookieAuthentication(null, IdentityOptions.ApplicationCookieAuthenticationScheme,
dataProtectionProvider: new DataProtectionProvider(
new DirectoryInfo(#"c:\shared-auth-ticket-keys\"),
configure =>
{
configure.ProtectKeysWithCertificate("thumbprint");
}));
The cookie authentication middleware will use the explicitly provided implementation of the DataProtectionProvider, which due to taking an explicit directory in its constructor is isolated from the data protection system used by other parts of the application.
Asp.net will use dataprotection which is injected by DI to encrypt cookies, in that case you need to enable owin auth in your classic asp.net application.
The first app is developed on Expressjs.
Authentication modules:
passport.js
express-session
cookie-parser
connect-mongo
session middleware
app.use(
session({
secret: 'kkkjfdfdf787dfdfdf7SSDSf7787asXZ',
store: new MongoStore({ url: mongoData.url, mongoOptions: {useUnifiedTopology: true}}),
name: 'nature_app',
rolling: true,
resave: true,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: process.env.MAX_SESS_AGE_SEC * 1000
}
})
)
Mongodb session collection after login gets data:
{
"_id" : "OtNPaWfyE6tpkfpIWh-m7XvCAp3No-JN",
"expires" : ISODate("2019-09-21T16:04:18.732+03:00"),
"session" : "{\"cookie\":{\"originalMaxAge\":3600000,\"expires\":\"2019-09-21T13:04:18.732Z\",\"secure\":false,\"httpOnly\":true,\"path\":\"/\"},\"csrfSecret\":\"rz_etmj7859KAC0OQRCPiyRF\",\"flash\":{},\"passport\":{\"user\":3143}}"
}
Cookie (nature_app) id:
s%3AOtNPaWfyE6tpkfpIWh-m7XvCAp3No-JN.8Yy2SKhGyt3NCEdka90JmEyrdphPas1yt6OHabDeqis
Now I'm developing the second app - admin panel with .Net Core 3, EntityFramework. Both apps will be into same domain. The first app: wow-nature.com, the second: wow-nature.com/admin.
If user was login into Expressjs app, and he clicked on admin panel link - .Net Core has to automatically login (like SSO). Admin panel won't have login panel.
I found three choices, but I doubt for security reasons:
.Net Core has to read Expressjs cookie id and check it in MongoDb. If a session valid - .Net Core will authenticate session. After it .Net Core has to regenerate session id. But my cookie id and mongodb id are different:
s%3AOtNPaWfyE6tpkfpIWh-m7XvCAp3No-JN.8Yy2SKhGyt3NCEdka90JmEyrdphPas1yt6OHabDeqis != OtNPaWfyE6tpkfpIWh-m7XvCAp3No-JN
And - is this method safe?
Jwt. I don't know how .Net Core works with web tokens. I read about authencation users from Laravel to Nodejs. In other places I read security doubts about it.
Auth0.
I'm confused. Sorry, I'm beginner in this situation.
What way .Net Core app authenticate from Expressjs app is more secure?
What are best practices?
I got answer from Asp.Net Core developer - it's not possible. For security reason we have not access to .Net core cookie modification.
So, I recreated app:
Main authentication with IdentityServer (OpenId).
Expressjs is client.
Other .Net Core app is client too.
Better way is to work by standard. No need reinvent bicycle.
I deployed an ASP.NET Core Web application on IIS, so far everything is ok. I have another ASP.NET MVC application that is using ASP.NET Identity and injecting a cookie AspNet.ApplicationCookie to the browser and afterwards redirecting me to my .Net Core app.
What I need to do is to get this user that was originally inserted on ASP.NET MVC on my .NET Core application and get asp identity authorized from my previous app, both apps have same domain so I can see the cookie in storage.
Question is: how can I configure the .NET Core app that its could get authenticated by ASP.NET cookie from previous application, also the ASP.NET MVC app is a blackbox for me.
This is what I tried so far:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddIdentityCookies();
app.UseAuthentication();
Update:
This is what i added to my code, but still doesn't get my previous identity
also i got some unfamilliar execeptions
services.AddAuthentication(option=> {
new CookieAuthenticationOptions { Cookie = {
Domain = ".dev.*****.ru",
Path ="/",
Name= ".AspNet.ApplicationCookie",
}
};
}).AddIdentityCookies(option=>new CookieAuthenticationOptions
{
Cookie = {
Domain = ".dev.*****.ru",
Path ="/",
Name= ".AspNet.ApplicationCookie",
}
});
[2019-04-12 11:25:51.903 +03:00 WRN] Microsoft.AspNetCore.DataProtection.Repositories.EphemeralXmlRepository
Using an in-memory repository. Keys will not be persisted to storage.
[2019-04-12 11:25:51.943 +03:00 WRN] Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager
Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.
[2019-04-12 11:25:51.977 +03:00 WRN] Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager
No XML encryptor configured. Key "41c89830-2ef1-48f7-afa5-16680973e6f2" may be persisted to storage in unencrypted form.
I have ASP.NET 4.5 service with OWIN pipeline that issues OAuth access_token in exchange for username/password. This service is called from ng-app and once it gets a token, stores in the browsers local storage. Then it calls resource api that consumes this token, API is also written in asp.net 4.5 and uses owin. This being OAuth token issued with OWIN it's encrypted/signed to machineKey secrets - so far so good and is happily consumed by the resource API. All this made possible by OAuthAuthorizationServerMiddleware.
Now I need to consume these tokens sent from the same ng-app to asp.net core 2.1 services, nothing fancy, just verify/decode it and get claims inside the token.
This OAuthAuthorizationServerMiddleware was never ported to asp.net core so I am stuck. (OAuth Authorization Service in ASP.NET Core does not help it talks about the full-fledged oidc server I just need to consume them w/o changing the issuing code)
In ConfigureServices() tacked on to service.AddAuthentication():
Tried.AddJwtBearer - but this makes no sense - these are not Jwt tokens really
Tried.AddOAuth but this does not make sense either b/c I am not dealing with full OAuth flow with redirects to obtain a token, I also don't deal with ClientId/ClientSecret/etc, I just receive "Bearer token-here" in the HTTP header from ng app so I need something in the pipeline to decode this and set ClaimsIdentity but this "something in the pipeline" also needs to have access to machinery-like data that is the same as it is in asp.net 4.5 OWIN service
Any ideas?
You could set the OAuthValidation AccessTokenFormat to use a MachineKey DataProtectionProvider and DataProtector which will protect and unprotect your bearer tokens. You will need to implement the MachineKey DataProtector. This guy already did it https://github.com/daixinkai/AspNetCore.Owin/blob/master/src/DataProtection/src/AspNetCore.DataProtection.MachineKey/MachineKeyDataProtectionProvider.cs.
public void ConfigureServices(IServiceCollection services){
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
ConfigureAuth(services);
string machineKey = #"<?xml version=""1.0"" encoding=""utf-8"" ?>
<machineKey decryption=""Auto"" decryptionKey =""DEC_KEY"" validation=""HMACSHA256"" validationKey=""VAL_KEY"" />";
var machineKeyConfig = new XmlMachineKeyConfig(machineKey);
MachineKeyDataProtectionOptions machinekeyOptions = new MachineKeyDataProtectionOptions();
machinekeyOptions.MachineKey = new MachineKey(machineKeyConfig);
MachineKeyDataProtectionProvider machineKeyDataProtectionProvider = new MachineKeyDataProtectionProvider(machinekeyOptions);
MachineKeyDataProtector machineKeyDataProtector = new MachineKeyDataProtector(machinekeyOptions.MachineKey);
//purposes from owin middleware
IDataProtector dataProtector =
machineKeyDataProtector.CreateProtector("Microsoft.Owin.Security.OAuth",
"Access_Token", "v1");
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddOAuthValidation(option=> {
option.AccessTokenFormat = new OwinTicketDataFormat(new OwinTicketSerializer(), dataProtector); })
It's important to keep the same DataProtector "purposes" Owin uses in the OAuthAuthorizationServerMiddleware so the data is encrypted/decrypted correctly. Those are "Microsoft.Owin.Security.OAuth", "Access_Token" and "v1". (see https://stackoverflow.com/a/29454816/2734166).
And finally you will have to migrate the Owin TicketSerializer (and maybe the TicketFormat too) since the one in NetCore is slightly different. You can grab it from here:
https://github.com/aspnet/AspNetKatana/blob/e2b18ec84ceab7ffa29d80d89429c9988ab40144/src/Microsoft.Owin.Security/DataHandler/Serializer/TicketSerializer.cs
I got this working recently. Basically authenticating to a .NET 4.5 Owin API and running a resource API in NET Core using the same token. I'll try to share the code in github as soon as I clean it up.
As far as I know it's not recommended to keep the old machine key data protector, but to migrate to the new ones from NET Core. Sometimes this is not possible. In my case I have too many APIs already in production, so I'm trying some new NET Core APIs to work with the legacy ones.
You should try this Owin.Token.AspNetCore nuget package instead. By following the code example provided in the README file I'm able to decode legacy tokens using the machine keys on .NET Core 3.1. Note: there's also an option to specify encryption method and validation method if the defaults are not working for you.
Does anyone know if it's possible to configure an Mvc4/WebApi2 application to validate/decrypt ASP.NET Core generated Anti Forgery Tokens?
We want to be able to, from an ASP.NET Core application, make a validated request to the WebApi2 application using the AntiForgeryToken generated by ASP.NET Core which runs on the same domain (subdomain).
I believe (and could be wrong, so please correct me) that this is possible under normal WebApi --> WebApi or ASP.NET Core --> ASP.NET Core scenarios provided both apps run under the same domain (and thus have access to cookies). I can't seem to find instruction on how to do this ASP.NET Core --> WebApi.
Our ASP.NET Core application has data protection services set up via:
services.AddDataProtection().PersistKeysToFileSystem("SomeDirectory");
The ASP.NET Core side has a working AntiforgeryToken implementation:
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
and
app.Use(next => context =>
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions {HttpOnly = false});
return next(context);
});
On the Mvc4/WebApi2 side we have a DataProtectorShim set up to allow it to be able to encrypt/decrypt OWIN cookies which both sides are able to operate on.
We tried to manually validate the anti forgery token/cookie but, unsurprisingly, it's unable to decrypt either (this was a stab in the dark):
AntiForgery.Validate(HttpContext.Current.Request.Cookies["XSRF-TOKEN"].Value, HttpContext.Current.Request.Headers["X-XSRF-TOKEN"]);
As far as I can tell, it looks like the System.Web Antiforgery class doesn't allow an injection point where we can push in a properly configured data protector/shim and always uses a MachineKey serializer to do this decryption. I'm aware of the Microsoft.AspNetCore.Antiforgery library but am unsure as to if/how this may be able to help.
Our failed attempt at this yields:
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
Which makes sense in that we're not using the machine key for encryption on the ASP.NET Core side.
Is anyone aware of a way to set this up? Any guidance is much appreciated.