How do I configure Owin to use a custom AuthenticationHandler? - c#

I have read that it's possible to create a custom Owin authentication handler, but I can't figure out how to configure Owin to use my handler instead of the default one.
How do I tell Owin to use this class instead of the default?
public class XDOpenIdAuthHandler: OpenIdConnectAuthenticationHandler
{
public XDOpenIdAuthHandler(ILogger logger)
: base(logger)
{
}
protected override void RememberNonce(OpenIdConnectMessage message, string nonce)
{
//Clean up after itself, otherwise cookies keep building up until we've got over 100 and
// the browser starts throwing errors. Bad OpenId provider.
var oldNonces = Request.Cookies.Where(kvp => kvp.Key.StartsWith(OpenIdConnectAuthenticationDefaults.CookiePrefix + "nonce")).ToArray();
if (oldNonces.Any())
{
CookieOptions cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
foreach (KeyValuePair<string, string> oldNonce in oldNonces)
{
Response.Cookies.Delete(oldNonce.Key, cookieOptions);
}
}
base.RememberNonce(message, nonce);
}
}

You must add it as a part of a custom AuthenticationMiddleware.
public class CustomAuthMiddleware : AuthenticationMiddleware<OpenIdConnectAuthenticationOptions>
{
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, authOptions)
{ }
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new XDOpenIdAuthHandler(yourLogger);
}
}
Then using it in the Startup.Auth for example:
public partial class Startup
{
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.Use<CustomAuthMiddleware>(new OpenIdConnectAuthenticationOptions());
}
}
Be aware however that the Owin pipeline must not contain the default OpenIdConnectAuthenticationMiddleware, otherwise it will still get called as part of the request pipe.

Related

Support multiple authorization filters for one action in ASP.NET Core

We have an IAuthorizationFilter and we can use it to authorize requests.
But there is one problem with it. I have to support multiple authorization filters, e.g.:
BearerTokenAuthorizationFilter
ApiKeyAuthorizationFilter
I would get behavior like this:
Firstly, BearerTokenAuthorizationFilter checks if authorization header starts with Bearer. If not, then if another authorization filter is applicable for current action, then let's run this another authorization filter. If there's no, then return an "HTTP 401 Unauthorized" error.
And the same with ApiKeyAuthorizationFilter. At the start, it checks if authorization header starts with Basic. If not, checks if another authorization filter exists and if yes then run it. Otherwise, return "HTTP 401 Unauthorized" error.
And to prevent infinite loop we need somehow store information that specific authorization filter was already performed.
It's easy to do in AuthenticationHandler. We can return AuthenticateResult.NoResult() when specific AuthenticationHandler is not applicable to handle this requests and another will run. If all were performed, then return 401 Unauthorized.
So I would have similar mechanism as exists for AuthenticationHandler (AuthenticateResult.NoResult()). How can I achieve this using AuthorizationFilter?
I think for your requirement,there should be three filters
Scheme Filter -Check if the auth scheme is correct ,if not correct,return fail
BearerTokenAuthorizationFilter -if the scheme is "Bearer",If yes then check the claims from the string ,return fail if claims are not correct
ApiKeyAuthorizationFilter -if the scheme is "Basic",If yes then check the user /Password from the string return fail if user /Password are not correct
I tried as below:
public class SchemeFilter : IAuthorizationFilter
{
private readonly string[] Schemes = new string[] { "Bearer", "Basic"};
private bool exist { get; set; }=false;
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Headers.ContainsKey("Authorization"))
{
var str = context.HttpContext.Request.Headers["Authorization"].ToString();
foreach (var scheme in Schemes)
{
exist=str.StartsWith(scheme);
if (exist)
{
break;
}
}
}
if (!exist)
{
context.Result = new UnauthorizedResult();
}
}
}
public class BasicFilter : IAuthorizationFilter
{
private readonly string Scheme = "Basic";
public void OnAuthorization(AuthorizationFilterContext context)
{
var check=context.HttpContext.Request.Headers.TryGetValue("Authorization", out var value);
if ( value[0].StartsWith(Scheme))
{
var basicstr = value[0];
//modify the logical here yourself
if (basicstr != "Basic expectedbasicstr")
{
context.Result = new UnauthorizedResult();
}
}
}
}
public class BearerFilter : IAuthorizationFilter
{
private readonly string Scheme = "Bearer";
public void OnAuthorization(AuthorizationFilterContext context)
{
var check = context.HttpContext.Request.Headers.TryGetValue("Authorization", out var value);
if ( value[0].StartsWith(Scheme))
{
var bearerstr = value[0];
//modify the logical here yourself
if (bearerstr != "Bearer expectedbearerstr")
{
context.Result = new UnauthorizedResult();
}
}
}
}
The result:
Multiple AuthorizeAttribute instances are processed by MVC as if they were joined with AND. If you want an OR behaviour you will need to implement your own logic for checks. Preferably implement AuthAttribute
There are two ways
Create custom Auth filter to implement your logic (Recommended)
Make your main endpoint receives the request, extract the type and redirect it to respective action for the type of authentication. So now you can look into headers and route to proper auth filter.
i think you can do a Policy based Authorization. So for this, you have to write a custom policy. you can check for multiple cases within a single policy
its explained here
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-7.0
i have done a similar thing using custom policies like below
public sealed class CompanyAccessRequirementHandler : AuthorizationHandler<CompanyAccessRequirement>
{
private readonly ILoggedInUserService _loggedInUserService;
private readonly IUserSecurityRespository _userSecurityRespository;
public CompanyAccessRequirementHandler(ILoggedInUserService loggedInUserService, IUserSecurityRespository userSecurityRespository)
{
_loggedInUserService = loggedInUserService;
_userSecurityRespository = userSecurityRespository;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CompanyAccessRequirement requirement)
{
if (context.User.Identity!.IsAuthenticated is false)
{
context.Fail();
return;
}
var userEmail = _loggedInUserService.UserEmail;
if (userEmail is null)
{
context.Fail();
return;
}
var user = await _userSecurityRespository.GetUser(context.User);
if (user is null)
{
context.Fail();
return;
}
var isSuperAdmin = _loggedInUserService.UserId == AppConstants.SuperAdmin.SuperUserId.ToString();
if (isSuperAdmin)
{
context.Succeed(requirement);
return;
}
var hasAccessToCompany = await _userSecurityRespository.HasAccessToCompanies(user.Id, _loggedInUserService.CompanyIds);
if (hasAccessToCompany)
{
context.Succeed(requirement);
return;
}
else
{
context.Fail();
}
}

Secure ASP controllers/methods programmatically

I'm after the API that allows to apply [Authorize] attribute from API rather than having it on controller's class/method.
Consider third party controller classes that come from dependency nuget package and I want to host them in my application and implement security for them.
I know I can use
app.UseEndpoints(endpoints => { endpoints.MapControllers().RequireAuthorization(new AuthorizeAttribute("policy")); });
but this will configure ALL controllers and methods to be protected by policy and I need to have control per class/method.
Mapping manually with
endpoints.MapPut("***").RequireAuthorization(new AuthorizeAttribute("someOtherPolicy"))
is also not an option since the mapping is already defined with [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("api/someRoute")] attributes on imported controllers.
Please advise
In case you can determinate on which endpoint should be specific authorization policy applied, you can try following:
public class MyAuthorizationFilter: AuthorizeAttribute, IAsyncAuthorizationFilter
{
public MyAuthorizationFilter() : base()
{
//this.AuthenticationSchemes = "";
//this.Policy = "";
//this.Roles = "";
}
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
context.Result = new OkObjectResult("my custom policy was checked");
return Task.CompletedTask;
}
}
public static class DynamicAuthorizationPolicies
{
public static void RegisterMyFilters(this IServiceProvider serviceProvider)
{
var allEndpoints = serviceProvider
.GetService<IActionDescriptorCollectionProvider>()
?.ActionDescriptors
?.Items;
if (allEndpoints is null)
{
return;
}
foreach (var endpoint in allEndpoints)
{
// If you debug hier you will see that you can register
// differnt filters or attribute in different collections
// for the endpoint
if (/*endpoint match some requirement*/true)
{
var authorizeFilter = serviceProvider.GetService<MyAuthorizationFilter>();
var descriptor = new FilterDescriptor(authorizeFilter, FilterScope.Action);
endpoint.FilterDescriptors.Add(descriptor);
}
}
}
}
... and here is the usage:
public void ConfigureServices(IServiceCollection services)
{
// services.Add...
services.AddTransient<MyAuthorizationFilter>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.Use....
app.ApplicationServices.RegisterMyFilters();
}

Extract User details from web api auth token in MVC

I am using a webapi project as my auth Server and also resource server. The intention is to access the serivice form an Android app. I also want a web front end which is being written in an MVC app. I originally used the default MVC auth but have moved to web pai handing out tokens. I can recieve the auth token form the webapi service and I am sending the token to the client in a cookie although I may just cache is client side. I currently have the following OAuthBearerAuthenticationProvider running:
public class CookieOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
base.RequestToken(context);
var value = context.Request.Cookies["AuthToken"];
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
and in my startup class I have this method:
private void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new CookieOAuthBearerProvider(),
});
}
which I call in the Configuration method.
The bit I seem to be missing is how to tap into converting my token into the logged in user. I cant seem to figure out where the deserializtion happens. I have tried changing my configueAuth to:
private void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new CookieOAuthBearerProvider(),
AccessTokenProvider = new AuthenticationTokenProvider()
{
OnReceive = receive
}
});
}
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
c.DeserializeTicket(c.Token);
c.OwinContext.Environment["Properties"] = c.Ticket.Properties;
});
and my receive method is being called. The AuthenticationTokenReceiveContext has my token attached but the DeserializeTicket is returning null. Can anyone advise what I am missing to get the User details form this token?
UPDATE as per suggested answer below. The Statrup code and OAuthBearerAuthenticationOptions now like like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
private void ConfigureAuth(IAppBuilder app)
{
OAuthOpt = new OAuthBearerAuthenticationOptions()
{
Provider = new CookieOAuthBearerProvider(),
AccessTokenProvider = new AuthenticationTokenProvider()
{
OnReceive = receive
}
};
app.UseOAuthBearerAuthentication(OAuthOpt);
}
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
var ticket = OAuthOpt.AccessTokenFormat.Unprotect(c.Token);
});
public static OAuthBearerAuthenticationOptions OAuthOpt { get; private set; }
}
but I am still getting a null value out. Could I be missing some relevant option on the OAuthBearerAuthenticationOptions?
Try this.
Save the OAuthBearerAuthenticationOptions you are instantiating inline to a static variable named OAuthOpt (or anything you like) in Startup.Auth and use the code below wherever you want to retrieve the user information.
Microsoft.Owin.Security.AuthenticationTicket ticket = Startup.OAuthOpt.AccessTokenFormat.Unprotect(token);`
I suggest you make use of Json Web Tokens (JWT) and customize the token generation using a CustomOAuthProvider. Here is a good resource from Taiseer Joudeh on how to do this. You will have to use this nuget package to decode the bearer tokens.

Custom OWIN CookieAuthenticationProvider fails on 1st/cold boot

We have a custom cookie auth provider that puts sets the auth cookie to bear a hostname like .domain.com instead of domain.com or my.domain.com. We do it so the cookies work across all subdomains and the domains. It's as simple as shown below.
Issue
On the very FIRST attempt after app cold start, the cookie STILL bears the domain my.domain.com (our logins are on my.domain.com) DESPITE setting it to .domain.com after executing the SubdomainCookieAuthentication code below (checked with breakpoints). On subsequent login attempts, the cookie hostname is fine.
Question
How can I fix this so it works even on the first attempt?
Code
Custom cookie auth
public class SubdomainCookieAuthentication : CookieAuthenticationProvider
{
public override void ResponseSignIn(CookieResponseSignInContext context)
{
// We need to add a "." in front of the domain name to
// allow the cookie to be used on all sub-domains too
var hostname = context.Request.Uri.Host;
// works for www.google.com => google.com
// will FAIL for www.google.co.uk (gives co.uk) but doesn't apply to us
var dotTrimmedHostname = Regex.Replace(hostname, #"^.*(\.\S+\.\S+)", "$1");
context.Options.CookieDomain = dotTrimmedHostname;
base.ResponseSignIn(context);
}
}
This is initialized inside the Owin startup class as follows
Class: Startup
File: App_start\Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new SubdomainCookieAuthentication()
});
}
I was having the same problem with the Cookie Domain not getting set on the first attempt using the ResponseSignIn method. I was able to resolve this by updating the Owin libraries to 3.x and using the new CookieManager to set the Domain. Found this solution from this post:
How is Owin able to set the Asp.Net Identity authentication cookies after the Application_EndRequest stage?
public class ChunkingCookieManagerWithSubdomains : ICookieManager
{
private readonly ChunkingCookieManager _chunkingCookieManager;
public ChunkingCookieManagerWithSubdomains()
{
_chunkingCookieManager = new ChunkingCookieManager();
}
public string GetRequestCookie(IOwinContext context, string key)
{
return _chunkingCookieManager.GetRequestCookie(context, key);
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
options.Domain = context.Request.Uri.GetHostWithoutSubDomain();
_chunkingCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
options.Domain = context.Request.Uri.GetHostWithoutSubDomain();
_chunkingCookieManager.DeleteCookie(context, key, options);
}
}
public static class UriExtensions
{
public static string GetHostWithoutSubDomain(this Uri url)
{
if (url.HostNameType == UriHostNameType.Dns)
{
string host = url.Host;
if (host.Split('.').Length > 2)
{
int lastIndex = host.LastIndexOf(".");
int index = host.LastIndexOf(".", lastIndex - 1);
return host.Substring(index + 1);
}
else
{
return host;
}
}
return null;
}
}
Then, register it in Startup.Auth.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new ChunkingCookieManagerWithSubdomains(),
...
}
);

Remove Cookie from Web API 2 Response

I am trying to do an authenticated web api request that does not reset the authentication cookie timeout. In the MVC world I would accomplish this by removing the FormsAuthenication cookie from the responce:
Response.Cookies.Remove(System.Web.Security.FormsAuthentication.FormsCookieName);
In Web API 2 I wrote a custom IHttpActionResult, and I am removing the Set-Cookie header from the response. This is however, not removing the header, as I still see the Set-Cookie header when the auth cookie is being updated for the requests that use this action result.
Here is the custom IHttpActionResult:
public class NonAuthResetResult<T> : IHttpActionResult where T: class
{
private HttpRequestMessage _request;
private T _body;
public NonAuthResetResult(HttpRequestMessage request, T body)
{
_request = request;
_body = body;
}
public string Message { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var msg = _request.CreateResponse(_body);
msg.Headers.Remove("Set-Cookie");
return Task.FromResult(msg);
}
}
How do I edit the response header in Web API 2, because this is not working.
Global.asax can remove cookies in the Application_EndRequest event. And you can set a variable to be later picked up by Application_EndRequest.
Step 1. Create an action filter which sets a variable in Context.Items:
public class NoResponseCookieAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
System.Web.HttpContext.Current.Items.Add("remove-auth-cookie", "true");
}
}
Step 2. Handle the Application_EndRequest event in your global.asax file. If the variable from Step 1 is present, remove the cookie.
protected void Application_EndRequest()
{
if (HttpContext.Current.Items["remove-auth-cookie"] != null)
{
Context.Response.Cookies.Remove(System.Web.Security.FormsAuthentication.FormsCookieName);
}
}
Step 3. Decorate your web api actions with the custom filter:
[NoResponseCookie]
public IHttpActionResult GetTypes()
{
// your code here
}
If you're using Web API 2, you're probably using the OWIN Cookie Middleware. What you are describing sounds like you want to disable the sliding expiry window on the auth cookie.
In the standard Web API template, you should have an App_Start/Startup.Auth.cs. In it you'll find the line...
app.UseCookieAuthentication(new CookieAuthenticationOptions());
This enables and configures the cookie middleware. You can pass in some options to change the timeout window and disable sliding expiry...
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
SlidingExpiration = false,
ExpireTimeSpan = new TimeSpan(1, 0, 0) // 1 hour
});

Categories

Resources