I have actually an asp.net website application, that can deliver token to an user with the following way :
the user logs into the application, go to a specific page and obtains a clientid and a clientsecret.
then, he calls the following api "....api/token" by giving clientid and clientsecret (client credentials grant type) to get the token.
This is the associated code :
using Microsoft.Owin;
using Owin;
using System;
using Microsoft.Owin.Security.OAuth;
[assembly: OwinStartup(typeof(MyApp.Web.App_Start.OwinStartup))]
namespace MyApp.Web.App_Start
{
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
OwinWebApiStartup.Configuration(app);
}
}
}
public static class OwinWebApiStartup
{
public static void Configuration(IAppBuilder app)
{
var provider = //my provider implementation;
var oauthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
Provider = provider,
};
app.UseOAuthAuthorizationServer(oauthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = //my provider implementation,
});
}
}
This is working nicely. But I would like to add a new feature, where a javascript client code, not an user anymore, would like to call my apis, and so it will need to have a token, but do not have a clientid and clientsecret.
This is my idea :
Create a new api endpoint, (only this one will be reachable by my javascript client code without token, and there, the code will generate a token (thanks to the username of the current user connected) and return this one (that will
be the same that an user could have obtained with the existing method) to be used by the javascript client code
I faced this problem in the past. I solved this via querystring, cause owin could only provide one token ressource. In fact it makes sense to rely on owin and on not implementing your own code.
My pseudo solution:
KeyValuePair<string, string[]> typePair = ctx.Request.Query.FirstOrDefault(x => x.Key == "type");
LoginType? loginType = GetLoginType(typePair);
[...]
switch (loginType)
{
case LoginType.User:
[...]
////within this routine you could set your claims depending on your needs
If you get another solution, I'd be grateful for sharing
Related
I have two application...... one is JavaScript signalR client and the other one is asp.net web application used as signalR server to broadcast the updates to the client. And I was trying to use azure active directory b2c service to offer authentication and authorization for user through client application to access the resources in the server. So, that only the authenticated user of JavaScript client can initiate signalR connection with the asp.net web application hosting signalR server after the token validation.
As, signalR uses web-sockets we cannot supply the token in the HTTP connection request header. It seems that I should use query string to supply authentication token in the signalR connection request.
After receiving that token in the asp.net server application I need to validate that token and allow the JavaScript client application to have a signalR connection.
I want to implement exactly the same thing in this blog post https://kwilson.io/blog/authorize-your-azure-ad-users-with-signalr/ but using azure active directory b2c.
It seems like others might also have same problem using ASP.NET SignalR Client and server architecture.
Actually, with lots of efforts I was able to solve this issue by customizing the AuthorizeModule of signalR hubs. Actually I override AuthorizeHubConnection() and AuthorizeHubMethodInvocation() using AuthorizeAttribute inheritance in CustomAuthorization class.
First of all I added the GlobalHost.HubPipeline.AddModule(module) in app.Map("/signalr", map =>{ .... } in startup Configuration. You can see it in the following startup.cs.
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Owin;
using Microsoft.AspNet.SignalR;
using TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration;
using Microsoft.AspNet.SignalR.Hubs;
[assembly: OwinStartup(typeof(TestCarSurveillance.RealTimeCommunication.Startup))]
namespace TestCarSurveillance.RealTimeCommunication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
//After adding Authorization module in GlobalHost.HubPipeline.AddModule(module)
//program was unable to create the log file so I have added it.
log4net.Config.XmlConfigurator.Configure();
// Branch the pipeline here for requests that start with "/signalr"
//app.UseWelcomePage("/");
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true,
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
EnableJSONP = true
};
// Require authentication for all hubs
var authorizer = new CustomAuthorization();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
map.RunSignalR(hubConfiguration);
});
}
}
}
This Authorize module calls CustomAuthorize.cs class in each signalR hub OnConnected(), OnDisconnected(), OnReconnected() and hub methods that the client can call.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Owin;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
public class CustomAuthorization : AuthorizeAttribute
{
// These values are pulled from web.config for b2c authorization
public static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string signUpInPolicy = ConfigurationManager.AppSettings["ida:SignUpInPolicyId"];
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//This method is called multiple times before the connection with signalR is established.
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
var metadataEndpoint = string.Format(aadInstance, tenant, signUpInPolicy);
// Extract JWT token from query string.
var userJwtToken = request.QueryString.Get("Authorization");
if (string.IsNullOrEmpty(userJwtToken))
{
return false;
}
// Validate JWT token.
//var tokenValidationParameters = new TokenValidationParameters { ValidAudience = ClientId };
//Contains a set of parameters that are used by a SecurityTokenHandler when validating a SecurityToken.
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
// This is where you specify that your API only accepts tokens from its own clients
// here the valid audience is supplied to check against the token's audience
ValidAudience = clientId,
ValidateIssuer = false,
// It is the authentication scheme used for token validation
AuthenticationType = signUpInPolicy,
//SaveSigninToken = true,
//I’ve configured the “NameClaimType” of the “TokenValidationParameters” to use the claim named “objectidentifer” (“oid”)
//This will facilitate reading the unique user id for the authenticated user inside the controllers, all we need to call
//now inside the controller is: “User.Identity.Name” instead of querying the claims collection each time
//Gets or sets a String that defines the NameClaimType.
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier"
};
try
{
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
if(authenticationTicket != null && authenticationTicket.Identity !=null && authenticationTicket.Identity.IsAuthenticated)
{
var email = authenticationTicket.Identity.FindFirst(p => p.Type == "emails").Value;
// It is done to call the async method from sync method
//the ArgumentException will be caught as you’d expect, because .GetAwaiter().GetResult() unrolls the first exception the same way await does.
//This approach follows the principle of least surprise and is easier to understand.
// set the authenticated user principal into environment so that it can be used in the future
request.Environment["server.User"] = new ClaimsPrincipal(authenticationTicket.Identity);
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
log.Error(ex);
//throw ex;
}
return false;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
//Check the authenticated user principal from environment
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
//ClaimsPrincipal supports multiple claims based identities
var principal = environment["server.User"] as ClaimsPrincipal;
if(principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)
{
// create a new HubCallerContext instance with the principal generated from token
// and replace the current context so that in hubs we can retrieve current user identity
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
return true;
}
return false;
}
}
}
After we receive the token from the query string we need to setup TokenValidationParameters use it in metadataEndpoint for token validation. The token validation is done in before having the hub connection so, that only the authorized user can have a connection and if the connection is not successful it returns 401 response. It is implemented in OpenIdConnectCachingSecurityTokenProvider.cs class. This class is being used by having following line of code in AuthorizeHubConnection() method.
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
As, the last part of this authorization configuration I have inherited IIssureSecurityKeyProvider in OpenIdConnectCachingSecurityTokenProvider.cs class. The complete implementation of it can be seen in the following code.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
//using System.IdentityModel.Tokens;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
//IIssuerSecurityKeyProvider Interface Provides security Key information to the implementing class.
// This class is necessary because the OAuthBearer Middleware does not leverage
// the OpenID Connect metadata endpoint exposed by the STS by default.
internal class OpenIdConnectCachingSecurityTokenProvider : IIssuerSecurityKeyProvider
{
//Manages the retrieval of Configuration data.
public ConfigurationManager<OpenIdConnectConfiguration> _configManager;
private string _issuer;
private IEnumerable<SecurityKey> _keys;
//this class will be responsible for communicating with the “Metadata Discovery Endpoint” and issue HTTP requests to get the signing keys
//that our API will use to validate signatures from our IdP, those keys exists in the jwks_uri which can read from the discovery endpoint
private readonly string _metadataEndpoint;
//Represents a lock that is used to manage access to a resource, allowing multiple threads for reading or exclusive access for writing.
private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();
public OpenIdConnectCachingSecurityTokenProvider(string metadataEndpoint)
{
_metadataEndpoint = metadataEndpoint;
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint);
RetrieveMetadata();
}
/// <summary>
/// Gets the issuer the credentials are for.
/// </summary>
/// <value>
/// The issuer the credentials are for.
/// </value>
public string Issuer
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _issuer;
}
finally
{
_synclock.ExitReadLock();
}
}
}
/// <summary>
/// Gets all known security keys.
/// </summary>
/// <value>
/// All known security keys.
/// </value>
public IEnumerable<SecurityKey> SecurityKeys
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _keys;
}
finally
{
_synclock.ExitReadLock();
}
}
}
private void RetrieveMetadata()
{
_synclock.EnterWriteLock();
try
{
//Task represents an asynchronous operation.
//Task.Run Method Queues the specified work to run on the ThreadPool and returns a task or Task<TResult> handle for that work.
OpenIdConnectConfiguration config = Task.Run(_configManager.GetConfigurationAsync).Result;
_issuer = config.Issuer;
_keys = config.SigningKeys;
}
finally
{
_synclock.ExitWriteLock();
}
}
}
}
After implementing this we do not need to have [Authorize] attribute in any hub method and this middle-ware will handle the request authorization and only authorized user will have a signalR connection and only authorized user can invoke the hub method.
At last I would like to mention that for this client server architecture to work we need to have separate b2c tenant client application and b2c tenant server application and b2c tenant client application should have API access to the b2c tenant server application. Azure b2c application should be configured as in this example https://learn.microsoft.com/en-us/aspnet/core/security/authentication/azure-ad-b2c-webapi?view=aspnetcore-2.1
Although, it is for .net core but it is also valid for asp.net and only difference is that b2c configuration should be at web.config
I have inherited a project that has been developed using OWIN, Server Middleware that manages a kind-of WebApi in order to communicate with mobile devices using ApiKeys. The Server side has a small web interface (which really is a set of test pages) but did not have authentication added. I am trying to wrap my head around the different frameworks being used and the ways one can authenticate around these OWIN techniques.
Let me show what I have first:
public class Startup
{
public void Configuration(IAppBuilder app)
{
log.Info("RT Server app starting up ...");
// Initialize the ApiKey Needed for ApiClient Library
ApiClient.ApiKey = Globals.ApiKey;
// Initialize the Services Library
Services.Startup.Initialize();//creates a configuration map of values for devices
// Setup Server Middleware
app.Use(typeof(ServerMiddleware), "RTrak.Server", "RTrak.Server");
app.Use(typeof(ServerMiddleware), "RTrak.Server.Pages", "RTrak.Server");
// HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];//throws an KeyNotFoundException
// listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
//ConfigureAuth(app)
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = MyAuthentication.ApplicationCookie,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider(),
CookieName = "RTrakCookie",
CookieHttpOnly = true,
ExpireTimeSpan = TimeSpan.FromHours(12), // ...
});
}
the ServerMiddleware
public ServerMiddleware(OwinMiddleware next, string baseNamespace, string defaultClass) : base(next)
{
BaseNamespace = baseNamespace;
DefaultClass = defaultClass;
}
public override async Task Invoke(IOwinContext owinContext)
{
var absolutePath = owinContext.Request.Uri.AbsolutePath;
string serverNamespace = BaseNamespace;
Type type;
string classToLoad = "";
if (absolutePath == "/")
classToLoad = DefaultClass;
else
{
classToLoad = absolutePath.Substring(1).Replace('/', '.');
if (classToLoad.EndsWith("."))
classToLoad = classToLoad.Substring(0, classToLoad.Length - 1);
}
type = Type.GetType($"{serverNamespace}.{classToLoad}, {serverNamespace}", false, true);
if (type == null)
{
classToLoad += ".Default";
type = Type.GetType($"{serverNamespace}.{classToLoad}, {serverNamespace}", false, true);
}
if (type != null)
{
try
{
object objService = Activator.CreateInstance(type);
((Resource)objService).Execute(owinContext);
}
catch (System.MissingMethodException)
{
//"403 INVALID URL");
}
}
else
await Next.Invoke(owinContext);
}
}
That ServerMiddleware is first calling Default Pages class that is HTML markup which links to the other test Pages
A thought was to add an MVC LoginController with AdAuthenticationService managing cookies Model to manage login that is configured as part of the Startup noted in the line ConfigAuth(app), but the middleware is ignoring the controller. Is MVC appropriate here?
then, I am looking at this ServerMiddleware and trying to understand how to intercept the Default page browser call with ActiveDirectory authentication.
I know that I may be overlooking something. Many thanks for anything (suggestions or resources) you can offer to help clear up this confusion for me.
What I did to resolve this was to leave the OWIN Middleware objects alone except for Startup.cs had to define CookieAuthentication route
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new Microsoft.Owin.PathString("/Auth/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
for the pages that were built as OWIN Resources. These OWIN Resource "Pages" then check
if (!this.Context.Authentication.User.Identity.IsAuthenticated)
I then implement an MVC controller that uses the AdAuthenticationService as above and UserManager to manage the AD credentials and Identity where the OWIN resource redirects to the MVC view+controller for authentication. That controller handles the login page actions. Upon authentication, the MVC redirects to the OWIN resource pages.
Thus, OWIN Middleware and MVC can live side-by-side so long as OWIN does not try to define the routes that MVC wants to use. Owin can maintain its own authentication as well.
So I'm trying to sort out web-based authentication using the WSFederation protocal. We've sorted out the setup, and my web app can reach the login page, after some headache:
(Asp.net on-premises authentication - The remote certificate is invalid according to the validation procedure)
Now I'm getting a 'IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken' error. From what I understand, we'll need middleware to deal with the security tokens. So I'm trying to get started with this:
https://www.scottbrady91.com/Katana/WS-Federation-Token-Encryption-using-Microsoft-Katana
So I've set the TokenValidationParameters option in the WsFederationAuthenticationOptions, but I'm getting an error from VisualStudio saying that 'Cert' does not exist in the current context. I'm confused as to why, as my code is nearly identical to the guides.
I'm also wondering if our certificate has simply been improperly configured. I came across some SSL guidelines for ADFS, and I know our IT guy hasn't gone down that road (yet). I'd like to rule that out as a possible cause, but if someone knows that it is, or is not, the cause, it'd save me time and be greatly appreciated.
EDIT: After some reading, there are some things that are unclear to me? We're using an ADFS server to handle the credentials, but as I understand it, ADFS should also handle our tokens without any additional work. Am I wrong? Should I be using middleware? Or is there a problem with the ADFS server configuration?
using System;
using System.Collections.Generic;
using System.Configuration;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.WsFederation;
using Owin;
using System.Security.Cryptography.X509Certificates;
using System.Security;
using System.Net.Security;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.IdentityModel.Tokens;
using Microsoft.Owin;
using RCHHRATool;
using System.Net;
using System.IdentityModel.Selectors;
namespace RCHHRATool
{
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
private X509Certificate2 certificate;
public void ConfigureAuth(IAppBuilder app)
{
Debug.WriteLine("Configure Auth Started");
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType =
WsFederationAuthenticationDefaults.AuthenticationType });
//System.Net.ServicePointManager.ServerCertificateValidationCallback.
//ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCB;
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach(X509Certificate2 cert in store.Certificates)
{
Debug.WriteLine(cert.Issuer);
if (cert.Issuer.Equals("CN=xxxxx.xxxxx.com"))
{
this.certificate = new X509Certificate2(cert);
}
}
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
Wtrealm = "https://localhost:44340",
Wreply = "http://localhost:56879",
MetadataAddress = adfsMetadata,
AuthenticationType = "adfs",
SecurityTokenHandlers = new SecurityTokenHandlerCollection
{
new X509SecurityTokenHandler
{
Configuration = new SecurityTokenHandlerConfiguration
{
IssuerTokenResolver = new X509CertificateStoreTokenResolver(StoreName.Root,
StoreLocation.LocalMachine)
}
}
}
//},
//TokenValidationParameters = new TokenValidationParameters
//{
// ValidAudience = "https://localhost:44340/",
// ValidIssuer = "xxxxx.xxxxx.com",
// IssuerSigningToken = new X509SecurityToken(this.certificate)
//}
});
}
Turns out that asp.net (framework 4.6.1) & ws-federation doesn't handle encrypted security tokens out of the box. I followed a great guide to resolve the token error. After some tuning (watch your certificate footprint, and make sure your certificates are in trusted root), I managed to get the authentication working.
I've been trying to understand how the reset password & account confirmation works in ASP.NET Identity. I'd just like to know if the Tokens are being stored and if so, where?
The links I receive when I'm using the password reset feature look something like this
http://localhost:1470/Account/ResetPassword?userId=a8b1389c-df93-4dfc-b463-541507c1a4bc&code=yhUegXIM9SZBpPVbBtv22kg7NO7F96B8MJi9MryAadUY5XYjz8srVkS5UL8Lx%2BLPYTU6a6jhqOrzMUkkMyPbEHPY3Ul6%2B%2F0s0qQvtM%2FLLII3s29FgkcK0OnjX46Bmj9JlFCUx53rOH%2FXMacwnKDzoJ1rbrUyypZiJXloIE50Q6iPuMTUHbX9O%2B3JMZtCVXjhhsHLkTOn9IVoN6uVAOMWNQ%3D%3D
My guess is that the tokens are stored in the link itself since I cannot find any trace of it anywhere else. Maybe someone knows for sure?
As I mentioned in the comment
"Tokens are generated using the SecurityStamp and validating against the SecurityStamp and not storing anywhere in database or local file storage. If you update the SecurityStamp, then previous tokens are no longer valid."
#DSR is correct but I would like to add some information to this as well.
If you have set up a Web project with Individual User Accounts go to:
App_Start -> IdentityConfig.cs
There you will see code like this:
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
The description for DataProtectorTokenProvider<TUser, TKey> gives the information:
Represents a token provider that uses an IDataProtector to generate
encrypted tokens based off of the security stamp.
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)
We can however try to dig a bit deeper how it really works. The token verification will fail if different Application Pool Identities are used for creating and validating a token on a single server. This points to that the actual protection mechanism would look something like this:
System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);
Given that it works if all sites use the same Application Pool Identity points to this as well. Could also be DataProtectionProvider with protectionDescriptor "LOCAL=user". It should have worked with different Application Pool Identities if LOCAL=machine was set.
new DataProtectionProvider("LOCAL=user")
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotector?view=netframework-4.7.2
https://learn.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider
dataProtectionProvider is of type IDataProtectionProvider.
It is injected in Startup.Auth.cs like this:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
CreatePerOwinContext is located in the assembly Microsoft.AspNet.Identity.Owin -> AppBuilderExtensions.cs. Both ASP.NET Identity and ASP.NET Core Identity are open source and can be viewed at GitHub.
public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app,
Func<IdentityFactoryOptions<T>, IOwinContext, T> createCallback,
Action<IdentityFactoryOptions<T>, T> disposeCallback) where T : class, IDisposable
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (createCallback == null)
{
throw new ArgumentNullException("createCallback");
}
if (disposeCallback == null)
{
throw new ArgumentNullException("disposeCallback");
}
app.Use(typeof (IdentityFactoryMiddleware<T, IdentityFactoryOptions<T>>),
new IdentityFactoryOptions<T>
{
DataProtectionProvider = app.GetDataProtectionProvider(),
Provider = new IdentityFactoryProvider<T>
{
OnCreate = createCallback,
OnDispose = disposeCallback
}
});
return app;
}
https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
https://archive.codeplex.com/?p=aspnetidentity#src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
app.GetDataProtectionProvider() is in turn located in assembly Microsoft.Owin.Security that is also Open Source.
public static IDataProtectionProvider GetDataProtectionProvider(this IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
object value;
if (app.Properties.TryGetValue("security.DataProtectionProvider", out value))
{
var del = value as DataProtectionProviderDelegate;
if (del != null)
{
return new CallDataProtectionProvider(del);
}
}
return null;
}
https://github.com/aspnet/AspNetKatana/blob/release/src/Microsoft.Owin.Security/DataProtection/AppBuilderExtensions.cs
We can also see that CreateDataProtector has a fallback to the implementation DpapiDataProtectionProvider.
private static IDataProtectionProvider FallbackDataProtectionProvider(IAppBuilder app)
{
return new DpapiDataProtectionProvider(GetAppName(app));
}
When reading about DpapiDataProtectionProvider(DPAPI stands for Data Protection Application Programming Interface) the description says:
Used to provide the data protection services that are derived from the
Data Protection API. It is the best choice of data protection when you
application is not hosted by ASP.NET and all processes are running as
the same domain identity.
The Create method purposes are described as:
Additional entropy used to ensure protected data may only be
unprotected for the correct purposes.
The protector class itself then looks like this:
using System.Security.Cryptography;
namespace Microsoft.Owin.Security.DataProtection
{
internal class DpapiDataProtector : IDataProtector
{
private readonly System.Security.Cryptography.DpapiDataProtector _protector;
public DpapiDataProtector(string appName, string[] purposes)
{
_protector = new System.Security.Cryptography.DpapiDataProtector(appName, "Microsoft.Owin.Security.IDataProtector", purposes)
{
Scope = DataProtectionScope.CurrentUser
};
}
public byte[] Protect(byte[] userData)
{
return _protector.Protect(userData);
}
public byte[] Unprotect(byte[] protectedData)
{
return _protector.Unprotect(protectedData);
}
}
}
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)
My goal is:
To use custom headers with my own token to authenticate a user or machine against my signalr service.
We've been using this methodology succesfully under ASP.net WEB API to perform our own custom claims based authentication and authorization.
Our Web Api was as follows:
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthorizationHeaderHandler());
}
Then we would have a AuthorizationHandler that would overwrite the Thread.CurrentPrincipal = principal; and we would be done.
Within SignalR I have tried to implement:
1. Mark our hub using Authorize
2. Implemented custom authorize atributes
3. Tried A Custom Module. But besides returning true if the correct headers we're send I still do not get the Context.User to change to the claims based principal that we generate.
But never can we get the Context.User to show the actual user that's being used to connect to the hub.
Any suggestions are Welcome.
Main reason why we want to achieve this is because we have a couple of different user/machine types that connect to our system.
Anybody any suggestions.
Finally found the solution.
I added my own owin security middleware allowing me to handle customer header based authentication.
This could be easily expanded allowing you to combine multiple authenitication scheme's within on service.
First Create Custom Authentication Middleware:
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) :
base(next) { }
public override async Task Invoke(IOwinContext context)
{
var request = context.Request;
var value = request.Headers["Phocabby-MachineKey"];
var username = value;
var usernameClaim = new Claim(ClaimTypes.Name, username);
var identity = new ClaimsIdentity(new[] { usernameClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
principal.Identities.First().AddClaim(new Claim("CanGetApiKey", "False"));
principal.Identities.First().AddClaim(new Claim("Cabinet", "True"));
request.User = principal;
await Next.Invoke(context);
}
}
Then register it in the startup class
public void Configuration(IAppBuilder app)
{
app.Use(typeof(AuthenticationMiddleware));
app.MapSignalR();
}