In self-hosted OWIN Web API, how to run code at shutdown? - c#

I am self-hosting a OWIN Web API using these code snippets:
class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
var route = config.Routes.MapHttpRoute("DefaultApi", "{controller}");
appBuilder.UseWebApi(config);
}
}
WebApp.Start<Startup>("http://localhost:8080")
I would like to run some code when my Web API service shuts down. I'm looking for something like HttpApplication.Application_End, a Disposed event, or a well-placed override void Dispose().
How do I run code when the Web API service shuts down?

I think there is a better way to get the CancellationToken:
var properties = new AppProperties(app.Properties);
CancellationToken token = properties.OnAppDisposing;
AppProperties is under namespace Microsoft.Owin.BuilderProperties, which comes from this nuget package: http://www.nuget.org/packages/Microsoft.Owin/
The description of property OnAppDisposing says:
Gets or sets the cancellation token for “host.OnAppDisposing”.
Please refer to: http://msdn.microsoft.com/en-us/library/microsoft.owin.builderproperties.appproperties%28v=vs.113%29.aspx

This can be achieved by getting the host's cancelation token and registering a callback with it like so
public class Startup
{
public void Configuration(IAppBuilder app)
{
var context = new OwinContext(app.Properties);
var token = context.Get<CancellationToken>("host.OnAppDisposing");
if (token != CancellationToken.None)
{
token.Register(() =>
{
// code to run
});
}
}
}
I was told by someone on the Katana team that this key is for host specific functionality and therefore may not exist on all hosts. Microsoft.Owin.Host.SystemWeb does implement this, but I'm not sure about the others.
The easiest way to verify if this will work for you is to check app.Properties for the host.OnAppDisposing key.

This is the same as arthas's answer but I've made it into an extension method
public static IAppBuilder RegisterShutdown(this IAppBuilder app, Action callback)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var properties = new AppProperties(app.Properties);
var token = properties.OnAppDisposing;
if (token != CancellationToken.None)
{
token.Register(callback);
}
return app;
}
Because then you can easily register shutdown actions like this
app.RegisterShutdown(() => Serilog.Log.CloseAndFlush());

Related

Common function called on every action

I have an ASP.NET Core web service running on Azure AppService. With every REST call, I check the auth0 token against my profile table to double-check they are a valid user.
Is there a way of refactoring this code out of every REST call, so it always run for every ASP.NET Core call coming in?
Here is a typical example of a REST call... (there are about 30 in all)
[Authorize]
[HttpGet("accounts/{accountId}")]
public async Task<ActionResult<ArchiveJob>> AccountGet(int accountId)
{
//--- common code repeated in every function
var profile = await RetrieveProfile();
if (profile is null)
{
return NotFound("Profile not found for this user")
}
// ... rest of function
return Ok();
}
The right way, is to use AddAuthorization to define your authorisation policies. Perhaps changing the default policy;
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireAssertion(context => {
// note; https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0#access-mvc-request-context-in-handlers
if (context.Resource is HttpContext c)
{
var something = c.RequestServices.GetRequiredService < ...> ();
var profile = await something.RetrieveProfile();
if (profile != null)
return true;
}
return false;
})
.Build();
});
You can create a IAuthorizationFilter to create a custom filter in your core application so it always runs for every ASP.NET Core call coming in.
public class MyAuthAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
//check access
if (CheckPermissions())
{
//all good, add some code if you want. Or don't
}
else
{
//DENIED!
//return "ChallengeResult" to redirect to login page (for example)
context.Result = new ChallengeResult(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
you can also find detail here

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;

How i can perform Token based Authentication in SignalR?

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.

Can't find OwinContext in NancyContext

I have a self hosted Owin application that uses Nancy. In one of the NancyModules I need to get an instance of IOwinContext.
This question touches on the subject, but there's no solution in it: Get current owin context in self host mode
It says that for Nancy, you have to use NancyContext to get to the Items dictionary and look for the value corresponding to the key "OWIN_REQUEST_ENVIRONMENT".
I do have access to the NancyContext and I can see the Items dictionary and that it contains a key called "OWIN_REQUEST_ENVIRONMENT". (I could also call the NancyContext.GetOwinEnvironment() extension, which gives the same result
However, when I get that key it doesn't contain an actual IOwinContext.
It contains a lot of keys with information about Owin (some of the keys are owin.RequestPath, owin.RequestMethod, owin.CallCancelled, and more), but not an actual context object. And it is only really a dictionary with various keys, so I can't cast it to an IOwinContext either.
How can I get from a NancyContext to an IOwinContext object?
public class MyStartup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
public class MyModule : NancyModule
{
Get["/", true] = async(x, ct) =>
{
var owinEnvironment = Context.GetOwinEnvironment();
// Now what?
}
}
var owinContext = new OwinContext(Context.GetOwinEnvironment());
example:
public class SecurityApi : NancyModule
{
public SecurityApi()
{
Post["api/admin/register", true] = async (_, ct) =>
{
var body = this.Bind<RegisterUserBody>();
var owinContext = new OwinContext(Context.GetOwinEnvironment());
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var user = new User {Id = Guid.NewGuid().ToString(), UserName = body.UserName, Email = body.Email};
var result = await userManager.CreateAsync(user, body.Password);
if (!result.Succeeded)
{
return this.BadRequest(string.Join(Environment.NewLine, result.Errors));
}
return HttpStatusCode.OK;
};
}
}
Actually, question that you mentioned has some tips that you probably missed.
For Nancy, you have to use NancyContext to get to the Items dictionary
and look for the value corresponding to the key
"OWIN_REQUEST_ENVIRONMENT". For SignalR, Environment property of
IRequest gives you access to OWIN environment. Once you have the OWIN
environment, you can create a new OwinContext using the environment.
So, once you called var owinEnvironment = Context.GetOwinEnvironment() and got the dictionary then you can create OwinContext (which is just wrapper for these dictionary values)
It has a constructor OwinContext(IDictionary<String, Object>) which, i guess, is what you need.
Also, you can get OwinContext from HttpContext:
// get owin context
var owinContext = HttpContext.Current.GetOwinContext();
// get user manager
var userManager = owinContext.GetUserManager<YourUserManager>();
I ended up solving this by creating new Owin middleware. In the middleware you have access to the current Owin context, which gives you access to the Owin environment.
When you have access to the Owin environment it's simply a case of adding the Owin context to the environment. When the context is in the environment you can retrieve it in the NancyModule.
After retrieving it like this I also had access to the GetUserManager() method on the context so that I could get my AspNetIdentity manager (as I mentioned in a comment to another answer). Just remember that the middleware must be added before Nancy to the Owin pipeline.
Startup
public class Startup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.Use(typeof(OwinContextMiddleware));
app.UseNancy();
}
}
Middleware
public class OwinContextMiddleware : OwinMiddleware
{
public OwinContextMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
context.Environment.Add("Context", context);
await Next.Invoke(context);
}
}
NancyModule
public class MyModule : NancyModule
{
public MyModule()
{
Get["/", true] = async(x, ct) =>
{
IDictionary<string, object> environment = Context.GetOwinEnvironment();
IOwinContext context = (IOwinContext)environment["Context"]; // The same "Context" as added in the Middleware
}
}
Caveat
The middleware listed above is untested as the middleware I have is more complex and I haven't had the time to create a working example. I found a simple overview on how to create Owin middleware on this page.

Middleware class not called on api requests

I've created a basic webAPI project (blank web project with webAPI checked) and added the owin nuget packages to the project.
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Owin
I've then created a Logging class, and hooked it up via startup
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
Debug.WriteLine("Startup Called");
var config = new HttpConfiguration();
WebApiConfig.Register(config);
appBuilder.UseWebApi(config);
appBuilder.Use(typeof(LoggingMiddleware));
}
}
public class LoggingMiddleware
{
private AppFunc Next { get; set; }
public LoggingMiddleware(AppFunc next)
{
Next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
Debug.WriteLine("Begin Request");
await Next.Invoke(environment);
Debug.WriteLine("End Request");
}
}
When I run the project, and the default page opens, I see the Begin/End requests called (twice, as it happens, not sure why that is).
However, if I try to call an /api route (such as `/api/ping/'), the request completes successfully, but I do not see the Begin/End request states in the log.
What am I missing with this?
Owin executes the middleware items in the order that they are registered, ending at the call to the controller (appBuilder.UseWebApi(config)) which does not appear to call next.Invoke(). Given that the code in the question has the Logging Middleware class registered after the UseWebApi call, this causes it to never be called for API requests.
Changing the code to:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
//.....
//This must be registered first
appBuilder.Use(typeof(LoggingMiddleware));
//Register this last
appBuilder.UseWebApi(config);
}
}
resolves the issue.

Categories

Resources