Owin
Owin is an abstraction layer for web hosting, with Katana being Microsoft's Owin wrapper for IIS/System.Web. You can also self-host with Owin (for webApi - MVC 5 still depends on system.web, but can use Owin middleware).
Owin was introduced with Visual Studio 2013/MVC 5/webApi 2/.net 4.5.1 (Oct 2013). WebApi 2 can entirely use Owin, but MVC5 just uses Owin middleware for authentication. .net Core depends entirely on an Owin-compatible stack (System.Web is no longer used)
Unlike System.Web, Owin is modular by design (and constructed from nuget packages). There are no interfaces or base classes- Startup.Configuration and Middleware.Invoke are by convention and are discovered by reflection.
The error if you forget is that HttpContext has no extension GetOwinContext().
Startup
Startup replaces global.asax ...
but you can have both (necessary for WebApi/MVC as MVC5 can't use OWIN)- Application_Start() runs first, then Startup.Configur[e|ation]().
- Owin looks for a class called "Startup" in the assembly namespace or
- [assembly: OwinStartup(typeof(MyNameSpace.MyStartup))] or
- appSettings/add key="owin:appStartup" value="NamedConfiguration" allows you to have named OwinStartupAttributes.
In ASP.Net 4.5 it uses method Configuration(IAppBuilder app)
In ASP.Net 5.0 it uses method Configure(IApplicationBuilder app)
public class Startup
{
//using Owin
public void Configuration(IAppBuilder app)
{
//use the "appFunc" - Func<IDictionary<string, object>, Task>
//the dictionary is the loosely typed HttpContext
app.Use(async (dictionary, nextTask) =>
{
Console.WriteLine("Requesting " + dictionary.Request.Path);
//next in pipeline
await nextTask();
});
//include webapi- must configure routes
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}",
new { Id = RouteParameter.Optional });
app.UseWebApi(config);
//Microsoft turns the "own.ResponseBody" stream into a typed Response.
app.Run(ctx => ctx.Response.WriteAsync("<p>Hello world</p>"));
}
}
Forms Authentication
See OWIN authentication - more
ASP.Net 4.5/MVC 5: Install-package:
- Microsoft.Owin.Security.Cookies
- Optional- DefaultAuthenticationTypes enum comes from Microsoft.AspNet.Identity.Core
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
}
AccountController
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel loginModel)
{
if (ModelState.IsValid && Validate(loginModel))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, loginModel.Username),
//NameIdentifier + IdentityProvider needed for anti-forgery
//or set AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
new Claim(ClaimTypes.NameIdentifier, loginModel.Username),
new Claim(
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
"Custom"),
};
var identity = new ClaimsIdentity(claims,
DefaultAuthenticationTypes.ApplicationCookie);
var authMgr = HttpContext.GetOwinContext().Authentication;
authMgr.SignIn(identity);
return RedirectToAction("Index", "Home");
}
return View(loginModel);
}
public ActionResult LogOff()
{
var authMgr = HttpContext.GetOwinContext().Authentication;
authMgr.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Login");
}
Self hosting (ASP.Net 4.5)
- Install-package Microsoft.Own.Hosting - depends on Microsoft.Owin, Owin.
- For base http.sys wrapper: Install-package Microsoft.Own.Host.HttpListener
- For webapi: Install-package Microsoft.AspNet.WebApi.OwinSelfHost
class Program
{
static void Main(string[] args)
{
using (Microsoft.Owin.Hosting.WebApp.Start<Startup>("http://localhost:8080"))
{
Console.WriteLine("Self hosting");
Console.ReadLine();
}
}
}
To make the console hosted in IIS:
- Install-package Microsoft.Owin.Host.SystemWeb
- Of course, cannot be an .exe, make it a class library with build into /bin, not /bin/Debug
Middleware
Middleware are pipeline components (like ASP's HttpModule), and chain themselves like decorators. You add them in Startup.Configuration with app.Use(lambda) or app.Use<MyMiddleware>(), or the equivalent extension method.
Example middleware: authentication, webapi, Microsoft.Owin.Diagnostics
All the state (equal to HttpContext) is in the IDictionary<string, object>
Standard keynames include:
- "owin.RequestBody" /"owin.ResponseBody" stream
- "owin.RequestPath" string
- "owin.RequestQueryString" string
- "owin.ResponseHeaders"/"owin.ResponseHeaders" IDictionary<string, string[]>
- "owin.ResponseStatusCode" int
- Katana extends with e.g. "server.User" IPrincipal
Katana 3 gives a typed IOwinContext with Request/ Response properties.
ASP.Net 5.0/MVC 6 has HttpContext (which isn't System.Web.HttpContext, but is based on IFeatureCollection)
public class MyMiddleware
{
private readonly Func<IDictionary<string, object>, Task> _next;
public MyMiddleware(Func<IDictionary<string, object>, Task> next)
{
_next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
//access the raw owin dictionary
Console.WriteLine("Requesting " + environment["owin.RequestPath"]);
//a katana content is easier
var context = new OwinContext(environment);
// authenticate
context.Request.User =
new GenericPrincipal(new GenericIdentity("Me"), null);
await _next(environment);
}
}
Authentication Middleware
There are built-in AuthenticationMiddleware classes.
- AuthenticationOptions - options including a name (AuthenticationType) and mode (active checks every message, or passive)
- AuthenticationHandler<options> - created each request, which extracts the security token from cookies/header/etc
- AuthenticationMiddleware<options> - has a CreateHandler method. This is hooked up with app.Use<middleware> or write an IAppBuilder extension.
public class CustomAuthenticationOptions : AuthenticationOptions
{
//ctor takes AuthenticationType
public CustomAuthenticationOptions()
: base("Custom")
{
}
//public string ExtraProperty { get; set; }
}
//per-request handler, using Microsoft.Owin.Security.Infrastructure.
public class CustomAuthentication :
AuthenticationHandler<CustomAuthenticationOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var authHeader = Request.Headers.Get("Authorization");
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Custom ", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var keyToken = authHeader.Substring("Custom ".Length).Trim();
//if this was Basic, you would Convert.FromBase64String, split username:password and validate
if (!string.Equals(keyToken, "Secret", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, keyToken),
new Claim(ClaimTypes.NameIdentifier, keyToken),
new Claim(
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
"Custom"),
};
var identity = new ClaimsIdentity(claims, "Custom");
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
return Task.FromResult(ticket);
}
}
public class CustomAuthenticationMiddleware :
AuthenticationMiddleware<CustomAuthenticationOptions>
{
public CustomAuthenticationMiddleware(OwinMiddleware next, CustomAuthenticationOptions options)
: base(next, options)
{
}
protected override AuthenticationHandler<CustomAuthenticationOptions> CreateHandler()
{
return new CustomAuthentication();
}
}
WebAPI
Changes from v1 to v2/owin
- You can still use web.config/HttpModule for security, but you should migrate to Owin middleware
- You can still use HttpMessageHandler : DelegatingHandler for security, but you should migrate to Owin middleware
- There is a new IAuthenticationFilter which fires before the Authorize filter.
The MVC and WebApi versions of these are different, but ultimately do the same thing. We'll get sanity back in vNext.
- V2 is claims based, and you can have aggregated identity from multiple authentications. You can define [HostAuthentication(name)] attributes to select the OWIN middleware (and [OverrideAuthentication] to do something specific on one method).
- User is different.
- In v1, ApiController.User == Thread.CurrentPrincipal. It may not match HttpContext.Current.User (used in MVC), so always set both of them.
- In v2, ApiController.User == RequestContext.Principal. In middleware, it hangs off the context.Request.
User may be null in v2, as well as not authenticated.
Basic authentication
- Webapi v1: AuthenticationFilter: west-wind
- v2 authenticationFilter: asp.net source code samples (MVC and Webapi)
- There's also a Thinktecture.IdentityModel middleware
Windows authentication
For self-hosting look at the underlying HttpListener to enable Windows authentication (asp.net).
public static IAppBuilder UseWindowsAuthentication(this IAppBuilder app)
{
object value;
if (app.Properties.TryGetValue("System.Net.HttpListener", out value))
{
var l = value as HttpListener;
if (l != null)
{
l.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
}
}
return app;
}