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.
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.
I am creating an authentication service using ASP.NET Core Identity and IdentityServer4. It is all working OK generally but I am having an issue due to the fact that in our live environment it is hosted across multiple servers, in which case a token (such as a password reset token) that is generated on one server is not valid on another.
I am setting up identity like so (custom user stores etc registered elsewhere):
services.AddDataProtection()
.SetApplicationName("IdentityService");
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireNonAlphanumeric = false;
})
.AddDefaultTokenProviders();
As you can see I have tried setting the application name so it is the same on each server, however this has not solved the issue.
I have found that I can create an IXmlRepository which will be used to store the keys used by Identity:
services.AddDataProtection()
.SetApplicationName("IdentityService");
.AddKeyManagementOptions(s => s.XmlRepository = new CustomXmlRepo());
If I implement this and store the keys in our database, will that solve my issue? Is there any other (better) way?
This article covers what you need to do to make an identityserver4 implementation production ready - amongst the requirements is shared and persistent storage of data protection keys:
IdentityServer4 guidance: http://docs.identityserver.io/en/latest/topics/deployment.html
Configure ASP.NET Core Data Protection: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1
Implementing your own method is absolutely fine - just be sure to ensure that wherever they are stored is adequately protected.
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.
I have a project that hosts the IdentityServer4 and I am attempting to also host in the same project a Web API, which accepts the access-token.
My question is, is possible that a single project contains the IdentityServer and an Web API that consume the same IdentityServer?
EDIT: The API must be secured with the Authorize attribute
I have an identity server 4 project, in the same project there is an API for CIUD of the clients. (Lets call it developer console api).
I then have a side project with is an asp .net core project that contains the actual razor pages for the Developer console it access the API within the Identity server project.
The reason i did it this way is that only one project should be updateing the database. So to update the database owned by the identity server it was decided the the API for accessing it should also be within the same project.
Yes you can have a web api from within your Identity server 4 project.
Configure service
services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// base-address of your identityserver
options.Authority = settingsSetup.Settings.Authority;
// name of the API resource
options.ApiName = "testapi";
options.RequireHttpsMetadata = false;
});
Configure
I think it needs to have both of these.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseAuthentication();
app.UseIdentityServer();
Endpoints
Because the requests are sent using the access token as a bearer token then the authorize for each of the API calls needs to include the authencationScheme. I havent exactly figured out why but without this it doesnt work.
[HttpGet("Client/List")]
[Authorize(AuthenticationSchemes = "Bearer")]
public ActionResult ClientList()
{
}
While #DaImTo's answer is correct and working and it's developed by IdentityServer team, it uses Introspection Endpoint which means for every request AddIdentityServerAuthentication will create a http request and send it to your server, which is the same app.
I developed a library called IdentityServer4.Contrib.LocalAccessTokenValidation which do the exact same thing but without using Introspection Endpoint. It will authenticate the token directly from TokenStore which is configured in Services. You can use it if you are interested.
nuget link : https://www.nuget.org/packages/IdentityServer4.Contrib.LocalAccessTokenValidation
github link : https://github.com/Kahbazi/IdentityServer4.Contrib.LocalAccessTokenValidation
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.