How i can perform Token based Authentication in SignalR? - c#

I am using signalR in asp.net mvc application,I want to authenticate cross
domain clients by token based authentication.I did not found complete solution for
it.
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
var hubConfiguration = new HubConfiguration
{
Resolver = GlobalHost.DependencyResolver,
};
map.RunSignalR(hubConfiguration);
});
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
public class impAuthHub : Hub
{
[Authorize]
public void SendMessage(string name, string message)
{
Clients.All.newMessage(name, message);
}
}
I dont know how i will get token to pass query string to my startup class?

You will be needed to use OAuth Bearer Token authentication with SignalR. and you need to use Microsoft’s OWIN Security and ASP.NET Identity libraries then include the WebAPI and Individual Accounts security options. This is a Full- Demo
Please find the code base for working sample git , which will help you.

Related

Azure AD Authentication using custom Authorize Attribute

I'm working on a web application built using .NET Framework 4.8. The application has one side for the public and one for admins.
The application is registered on Azure, and I'm trying to use Azure AD groups to authenticate and authorize users.
I setup the Middleware for Azure authentication in a a Startup.Auth.cs partial class. The code in the partial class is shown at below.
To access the admin side, the user has to type /admin in the URL which will go to the admin controller.
I use a custom Authorize attribute in the Admin Controller with a specific Role. It's used before the initialization of the AdminController class.
The custom Authorize class code is shown below.
Locally, the application seems to be working fine and it allows people in the group specified in the authorize attribute and redirects ones without access to the Home like we want.
When we publish the code to Production, the application always takes the user back to home when they try to go to the admin side.
The redirectURI is setup to be "https://example.com/admin" and it is added to the application in Azure.
Using Authorize attribute in the admin controller allows anyone that is part of the Azure AD for the tenant.
Please let me know if I'm missing anything or what your thoughts are
Startup.Auth.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["TenantId"];
private static string redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
private static string authority = aadInstance + tenantId + "/v2.0";
public void ConfigurationAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UserOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = clientId;
Authority = authority;
RedirectUri = redirectUri;
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) => {
context.HandleResponse();
context.Response.Redirect("Home/index");
return Task.FromResult(0);
}
}
});
}
}
Custom authorize attribute AuthorizeAttribute.cs:
public class AuthorizeAD : AuthorizeAttribute
{
private bool noPermission = false;
protected override bool AuthorizationCore(HttpContextBase httpContext)
{
if(!httpContext.User.Identity.IsAuthenticated)
return false;
var roles = Roles.Trim().Split(',');
if(roles.ToList().Exists(role => httpContext.User.IsInRole(role)))
{
return true;
}
else
{
noPermission = true;
return false;
}
}
protected override void HandleUnAuthorizedRequest(AuthorizationContext filterContext)
{
if(noPermission)
filterContext.Result = new RedirectResult("Home/index");
else
base.HandleUnauthorizedRequest(filterContext);
}
}
Any help, feedback, or suggestions would be great. Thank you in advance!
• You can capture the return URL using the request information instead of using the ‘custom AuthorizeAttribute’ which will make your ‘returnURL’ or redirect URI available within ‘Request.QueryString[]’ dictionary. Also, you will need to add the below in your login view to make it actionable and the below it in your login form: -
#{
ViewBag.ReturnUrl = Request.QueryString["returnUrl"];
}
#using (Html.BeginForm("Login", "Account", new {returnUrl = ViewBag.ReturnUrl}, FormMethod.Post, new{#class="form-horizontal form-material", #onsubmit="return loading_event();", #id="loginForm"}))
Please find the below SO community thread for more reference and clarification on this: -
Custom Authorize attribute with redirect to original URL
Also, since you want to use the custom ‘AuthorizeAttribute’ class with a specific role for access in the ‘AdminController’ class, you can surely use them by leveraging the role-based authorization for the actions in the ‘AdminController’ class as follows: -
String constants in ‘Auth.cs’ : -
public static class RoleConstants
{
public const string Admin = "Admin";
public const string Moderator = "Moderator";
// more roles
}
‘AdminController’ class is as below after the above constant inclusion: -
[Authorize(Roles=RoleConstants.Admin+","+RoleConstants.Moderator)]
public class AdminController : Controller
{
// ...
}
Please find the below link for more information regarding the above: -
https://www.telerik.com/blogs/creating-custom-authorizeattribute-asp-net-core

Blazor WASM + AAD B2C + Custom Authorisation

I have a Blazor client (WASM) app that integrates with AAD B2C for authentication.
After authentication, I want to call my own API for further authorisation information. The reason I want to do this rather than getting B2C to call my API is I will have series of different apps using the same B2C, with different claims, roles and other information etc.
I've tried every tutorial I can find, but nothing seems to wire up.
My Program.cs has this:
builder.Services.AddMsalAuthentication(options =>
{
var settings = config.AzureAdB2C;
var authentication = options.ProviderOptions.Authentication;
authentication.Authority = $"{settings.Instance}{settings.Domain}/{settings.SignInPolicy}";
authentication.ClientId = settings.ClientApplicationId;
authentication.ValidateAuthority = false;
options.ProviderOptions.DefaultAccessTokenScopes.Add($"{settings.ServerAppId}/{settings.DefaultScope}");
//options.ProviderOptions.Cache.CacheLocation = "localStorage";
});
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
And for example, I've tried this:
builder.Services.AddScoped<IClaimsTransformation, UserInfoClaims>();
public class UserInfoClaims : IClaimsTransformation
{
private static IEnumerable<SimpleClaim> roles;
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
...
But it doesn't get hit.
Is it possible to rewrite claims in WASM after B2C authentication?
And if not, is there an event I can wire up to after successful authentication to just manage my own role-like alternative?
This can be done by implementing your own AccountClaimsPrincipalFactory
public class ExampleClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount>
where TAccount : RemoteUserAccount
{
public ExampleClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{
//Any dependency injection or construction of objects
//inside this constructor usually leads to wasm memory exceptions
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (account != null)
{
//Add logic here to get custom user information
//Add Claims to the user identity like so
var identity = user.Identity as ClaimsIdentity;
identity.AddClaim(new Claim("type", "value"));
}
return user;
}
}
Then on start up when adding authentication you do the following
builder.Services.AddMsalAuthentication()
.AddAccountClaimsPrincipalFactory<ExampleClaimsPrincipalFactory<RemoteUserAccount>>();

How to authenticate a user when consuming MassTransit messages in Asp.Net Core Web API?

I have several Asp.Net Core Web APIs that use Bearer authentication and IdentityServer4.AccessTokenValidation middleware to introspect tokens, authenticate the user and create claims. This works fine for HTTP requests.
I am in the process of configuring these APIs to also be MassTransit endpoints (for both Publishing and Consuming messages) using RabbitMQ as transport. I followed the instructions here for adding MassTransit to the API and for setting up message consumers. A typical workflow will be something like:
HTTP Request to API > Publish message on MassTransit > RabbitMQ > Message consumed in another API
What I'm struggling to understand is how I can create a ClaimsPrincipal when consuming messages off the bus so that I know which user to perform actions on behalf of? Where it's not an HTTP request there is no AuthenticationHandler being invoked.
What I've tried so far:
I thought I'd approach this by passing a token (and/or individual claim values) in message headers. The publish part seemed easily enough as MassTransit allows adding any number of custom headers when publishing messages using MassTransit.PublishContextExecuteExtensions.Publish. This allowed me to get messages onto the transport with information identifying a user and this info can be viewed in a consumer by manually viewing the headers e.g.
public class SomeEventConsumer : IConsumer<SomeEventData>
{
public async Task Consume(ConsumeContext<SomeEventData> context)
{
var token = context.Headers["token"];
}
}
At this point I could take the token and call the Introspection endpoint in Identity Server manually but then I'd need to:
Do this in every consumer every time and then ...
... pass that information down to logic classes etc manually instead of making use of IHttpContextAccessor.HttpContext.User.Claims or by wrapping the claims and using Dependency Injection.
To address point 1 I created a new custom middleware ...
public class AuthenticationFilter<T> : IFilter<ConsumeContext<T>> where T : class
{
public void Probe(ProbeContext context)
{
var scope = context.CreateFilterScope("authenticationFilter");
}
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
var token = context.Headers.Where(x => x.Key == "token").Select(x => x.Value.ToString()).Single();
// TODO: Call token introspection
await next.Send(context);
}
}
public class AuthenticationFilterSpecification<T> : IPipeSpecification<ConsumeContext<T>> where T : class
{
public void Apply(IPipeBuilder<ConsumeContext<T>> builder)
{
var filter = new AuthenticationFilter<T>();
builder.AddFilter(filter);
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class AuthenticationFilterConfigurationObserver : ConfigurationObserver, IMessageConfigurationObserver
{
public AuthenticationFilterConfigurationObserver(IConsumePipeConfigurator receiveEndpointConfigurator) : base(receiveEndpointConfigurator)
{
Connect(this);
}
public void MessageConfigured<TMessage>(IConsumePipeConfigurator configurator)
where TMessage : class
{
var specification = new AuthenticationFilterSpecification<TMessage>();
configurator.AddPipeSpecification(specification);
}
}
public static class AuthenticationExtensions
{
public static void UseAuthenticationFilter(this IConsumePipeConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
_ = new AuthenticationFilterConfigurationObserver(configurator);
}
}
... and then added that into the pipeline ...
IBusControl CreateBus(IServiceProvider serviceProvider)
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host("rabbitmq://localhost");
cfg.UseAuthenticationFilter();
// etc ...
});
}
And this is where I'm stuck. I don't know how to authenticate the user for the scope of the request. Where it's not an HTTP request I'm not sure what best practice is here. Any suggestions or pointers would be gratefully received. Thanks...
I've just been watching a Kevin Dockx course on Pluralsight that covers this scenario on Azure Service Bus, but the same principal would apply to Mass Transit or any other asynchronous communication between services using a message bus. Here's a link to the section: Securing Microservices in ASP.NET Core
Kevin's technique is to include the access token (JWT) as a property on the bus message and to then validate this in the consumer using IdentityModel.
To summarise:
In the Producer:
Get the Access Token from the request (e.g. HttpContext.GetUserAccessTokenAsync()).
Set this as a property in the message before sending.
In the Consumer:
Use IdentityModel to get the IdP Discovery Document
Extract the public signing keys from the discovery response (these must be converted to RsaSecurityKey)
Call JwtSecurityTokenHandler.ValidateToken() to validate the JWT from the message. This returns a ClaimsPrincipal if successful.
If you're concerned about Access Token expiration, you can make use of the datetime that the message was enqueued as part of the token validation logic in the consumer.
Here's how the validator works (simplified):
var discoveryDocumentResponse = await httpClient.GetDiscoveryDocumentAsync("https://my.authority.com");
var issuerSigningKeys = new List<SecurityKey>();
foreach (var webKey in discoveryDocumentResponse.KeySet.Keys)
{
var e = Base64Url.Decode(webKey.E);
var n = Base64Url.Decode(webKey.N);
var key = new RsaSecurityKey(new RSAParameters
{ Exponent = e, Modulus = n })
{
KeyId = webKey.Kid
};
issuerSigningKeys.Add(key);
}
var tokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = "my-api-audience",
ValidIssuer = "https://my.authority.com",
IssuerSigningKeys = issuerSigningKeys
};
var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(tokenToValidate,
tokenValidationParameters, out var rawValidatedToken);
return claimsPrincipal;

Windows Authentication - require additional password for special users

I am developing an intranet asp.net core web api application. The requirements for authentications are:
REQ1 - when user which is trying to access the website is not in Active Directory's special group (let's name it "commonUsers") it is simply not authorized
REQ2 - when user which is trying to access the website is in Active Directory's group "commonUsers" is is authorized and a web resource is returned
REQ3 - when user which is trying to access the website is in Active Directory's group "superUser", it need to be prompted for his domain password once again (because it tries to access some very restricted resources)
Now, what I have so far:
My service is hosted using http.sys server in order to support windows authentication.
I am using claims transformer middlewere in order to check the user's Active Directory group, let's say something like this:
public class ClaimsTransformer : IClaimsTransformation {
private readonly IAuthorizationService _authorizationService;
public ClaimsTransformer(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_authorizationService.Authorize(principal as IHmiClaimsPrincipal);
return Task.FromResult(principal);
}}
I have specified a special policies also in my service configuration, for instance something like that:
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy =>
policy.RequireClaim(ClaimTypes.Role, "TestUser"));
options.AddPolicy("TestPolicy2", policy =>
policy.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
I am using [Authorize] attribute with specific policy in order to restrict access to specific resources based on policies
Now the question is, how should I satisfy REQ3?
I think I would try to use MVC Filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#authorization-filters
Filters run after all Middleware, but before the Action. This will allow you to control the redirect to credentials page just for specific actions or controllers. Whilst normally this is not the recommended method for authorization, I think it fits your requirements for a hybrid secondary authentication.
public class SuperUserFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Cookies.TryGetValue("SuperUserCookie", out string cookieVal))
{
if (!IsValidCookie(cookieVal))
context.Result = LoginPage(context);
}
else
{
context.Result = LoginPage(context);
}
}
private bool IsValidCookie(string cookieVal)
{
//validate cookie value somehow
// crytpographic hash, store value in session, whatever
return true;
}
private ActionResult LoginPage(AuthorizationFilterContext context)
{
return new RedirectToActionResult("SuperUser", "Login",
new {redirectUrl = context.HttpContext.Request.GetEncodedUrl()});
}
}
Then you create a Login Controller
public class LoginController : Controller
{
[HttpGet]
public IActionResult SuperUser(string redirectUrl)
{
// return a page to enter credentials
// Include redirectUrl as field
}
[HttpPost]
public IActionResult SuperUser(LoginData loginData)
{
// Validate User & Password
Response.Cookies.Append("SuperUserCookie", "SomeValue");
return Redirect(loginData.RedirectUrl);
}
}
Then you can decorate specific actions (or controllers) as required:
public class MyController : Controller
{
[HttpGet]
[SuperUserFilter]
public IActionResult MySensitiveAction()
{
// Do something sensitive
}
}
I'm guessing you are try to implement two step authentication for some of your resource.
To do that you must use multiple authentication scheme and Authorize policies,
but it's difficult because windows authentication is not controllable. we need to use some trick to know this is your second login.
authentication
The Default Authenticaiton Scheme : Windows, it's the basic scheme for authenticate a windows user.
Second Cookies base Authentication scheme : SuperUserTwoStep. we need this to goto our custom login logic.
Authorize
the Authorize policies for specified scheme.
a login page for login to SuperUserTwoStep scheme.
//startup
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme)
.AddCookie("SuperUserTwoStep",op=>op.LoginPath = "/account/superuser2steplogin");
services.AddAuthorization(op =>
{
op.AddPolicy("SuperUser", b => b.AddAuthenticationSchemes("SuperUserTwoStep")
.RequireAuthenticatedUser()
.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
// login
public static IDictionary<string, string> States { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
[Route("/account/superuser2steplogin")]
public async Task<IActionResult> LoginTwoStepConfirm(string returnUrl, [FromServices]IAuthorizationService authorizationService,
[FromServices]IAuthorizationPolicyProvider policyProvider)
{
var winresult = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (winresult.Succeeded)
{
if (States.TryGetValue(winresult.Principal.Identity.Name, out _))
{
States.Remove(winresult.Principal.Identity.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity(winresult.Principal.Claims,"twostepcookie"));
await HttpContext.SignInAsync("SuperUserTwoStep", principal);
return Redirect(returnUrl);
}
else
{
States[winresult.Principal.Identity.Name] = "1";
return Challenge(IISDefaults.AuthenticationScheme);
}
}
else
{
return Challenge(IISDefaults.AuthenticationScheme);
}
}
[Authorize("SuperUser")]
public IActionResult YourSecurePage()
{
return Content("hello world");
}
the most difficult thing is to track that this is the second time to login, I try to use cookie , but it doen't work, so I crate a static IDitionary<string,string> to track ,maybe use distributed cache is better
I think in my opinion you should consider using: Policy-based authorization with Requirements, basically you have different authorization requirements that you want to treat them on and AND basis
REQ1 and REQ2 and REQ3
Here you have the link to the documentation: Requirements
But you need to understand that identity != permissions, the guys that introduce this concept of policies to Microsoft created a project named: PolicyServer and it is opensource: PolicyServer Git and they created a pattern there of how you should use your policies. Basically, you have external and internal users that are authenticated against your AD, all internal users should have permissions assigned to a role. And you only decorate your controller action with the permission rule you created for that policy
[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
// omitted
}
To understand the code and how they evaluate a policy, I think you should see the video they have online on the website: Policy Server
Hope this helps

IdentityServer3 .Net Web API. Getting error - Authorization has been denied for this request

I am trying to setup authentication for my .net webapi using identityserver3.
This is my code in Owin.Startup of the Authentication server project
public class Startup
{
public void Configuration(IAppBuilder app)
{
// hardcoded list of clients, scopes and users
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(clients)
.UseInMemoryScopes(scopes)
.UseInMemoryUsers(users);
app.UseIdentityServer(new IdentityServerOptions
{
SigningCertificate = new X509Certificate2($#"{AppDomain.CurrentDomain.BaseDirectory}\bin\my_selfsigned_cert.pfx", ConfigurationManager.AppSettings["certificatekey"]),
RequireSsl = false,
Factory = factory
});
}
And the following is the code in my web api owin startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:45230"
});
app.UseWebApi(GlobalConfiguration.Configuration);
}
}
My authorization server seems to work when I try to login in the identity servers login page. I am also able to retrieve authorization token by posting to /connect/token
However when I use the bearer token thus received to call my webapi method below, it's always failing with error "{"Message": "Authorization has been denied for this request."}
Api -
[HttpGet]
[Authorize]
public IEnumerable<Customer> Get()
{
var customerRepository = new CustomerRepository();
return customerRepository.GetCustomers();
}
Can somebody please suggest what i am missing ?
Microsoft.Owin.Host.SystemWeb
Installed this nuget to my web api project as suggested in here and it started working !!

Categories

Resources