Adding OpenIddict 3.0 to an OWIN application

Up-to-date samples for ASP.NET 4.x and OWIN can be found in the openiddict/openiddict-samples repository:

  • Fornax: authorization code flow demo using ASP.NET Web Forms 4.8 and OWIN/Katana, with a .NET console acting as the client.
  • Kalarba: resource owner password credentials demo using OWIN/Katana, ASP.NET Web API and the OpenIddict degraded mode.

Last year, I announced that OpenIddict would become compatible with OWIN/Katana – and thus usable in any ASP.NET 4.x non-Core application – as part of the 3.0 release. While using OpenIddict in an OWIN application shouldn't be much different than using it in an ASP.NET Core application, there's actually a part than will slightly differ between the two platforms: dependency injection.

Globally introduced in ASP.NET Core 1.0, dependency injection support was not a thing in OWIN. Yet, OpenIddict itself depends on dependency injection for all its features: core, server and token validation. To work around the lack of official DI support, OpenIddict 3.0 will support two approaches: one working with the Autofac DI container and one without. This blog post briefly describes how to do that.

To keep things simple, we'll create a tiny self-hosted .NET 4.7.2 OWIN application, but of course, these 2 approaches also work in any ASP.NET application, no matter what's the host used: HttpListener, System.Web or even third-party options like Nowin.

For that, create a new application with the following .csproj, that contains the basic things we'll need: the Microsoft DI primitives used by OpenIddict, the OWIN self-host and hosting abstractions and the OpenIddict OWIN metapackage (that itself references the core, server and validation packages, plus the OWIN integration assembly):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.4" />
<PackageReference Include="Microsoft.Owin.Host.HttpListener" Version="4.1.0" />
<PackageReference Include="Microsoft.Owin.Hosting" Version="4.1.0" />
<PackageReference Include="OpenIddict.Owin" Version="3.0.0-beta1.20311.67" />
</ItemGroup>

</Project>

Then, add the entry point...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using Microsoft.Owin.Hosting;

namespace OpenIddictWebApiDemo
{
public static class Program
{
public static void Main(string[] args)
{
const string address = "http://localhost:58779/";

using (WebApp.Start<Startup>(address))
{
Console.WriteLine($"Server is running on {address}, press CTRL+C to stop.");
Console.ReadLine();
}
}
}
}

... and the Startup class:

1
2
3
4
5
6
7
8
9
10
11
using Owin;

namespace OpenIddictWebApiDemo
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
}
}
}

At this point, no matter the option you'll opt for, you'll need to instantiate a ServiceCollection and register the OpenIddict server and validation services (exactly like how you'd do in a ASP.NET Core application):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void Configuration(IAppBuilder app)
{
var services = new ServiceCollection();
services.AddOpenIddict()
.AddServer(options =>
{
options.AllowPasswordFlow();
options.EnableDegradedMode();
options.AcceptAnonymousClients();

options.SetTokenEndpointUris("/connect/token");

options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();

options.UseOwin()
.DisableTransportSecurityRequirement();

options.AddEventHandler<ValidateTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
// Client authentication is used in this sample,
// so there's nothing to validate here.

return default;
}));

options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
if (!context.Request.IsPasswordGrantType())
{
throw new InvalidOperationException("The specified grant type is not supported.");
}

// Validate the username/password parameters.
// In a real world application, you'd use likely use a key derivation function like PBKDF2 to slow
// the client secret validation process down and a time-constant comparer to prevent timing attacks.
if (!string.Equals(context.Request.Username, "alice@wonderland.com", StringComparison.Ordinal) ||
!string.Equals(context.Request.Password, "P@ssw0rd", StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidGrant,
description: "The username/password couple is invalid.");

return default;
}

var principal = new ClaimsPrincipal(new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType));
principal.SetClaim(Claims.Subject, "Bob");

context.Principal = principal;

return default;
}));
})

.AddValidation(options =>
{
options.UseLocalServer();

options.UseOwin();
});
}

If you read my previous post, you probably figured out that I used OpenIddict 3.0's degraded mode in this snippet to implement the OAuth 2.0 password flow, which allows using OpenIddict's server feature without any backing database.

If you try to run the project as-is, all you'll get is a 404 error, as there's still a thing we need to do: integrating OpenIddict into OWIN's request processing pipeline.

Unlike most other DI containers, Autofac has an excellent OWIN integration story that keeps things very clean and well-integrated. To use Autofac with OWIN, reference the following packages:

1
2
<PackageReference Include="Autofac.Owin" Version="5.0.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="6.0.0" />

Then, update your Configuration method to create the IContainer, import the ServiceCollection and register the Autofac OWIN middleware:

1
2
3
4
5
6
7
8
9
10
11
12
public void Configuration(IAppBuilder app)
{
var services = new ServiceCollection();

// ...

var builder = new ContainerBuilder();
builder.Populate(services);
var container = builder.Build();

app.UseAutofacMiddleware(container);
}

This is the beauty of this option: nothing else is required.

Using OpenIddict 3.0 without Autofac

Without Autofac, things will get a bit more complicated, as you'll need to manage per-request scopes yourself. While we'll use the Microsoft DI container to achieve that, this approach is also doable with any other DI container that provides an IServiceProvider implementation.

For that, we'll have to add a middleware creating a service scope per request and injecting it in the OWIN environment with the System.IServiceProvider key (the value that the OpenIddict server and validation OWIN integrations expect). We'll also need to call app.UseOpenIddictServer() and app.UseOpenIddictValidation(), that register the OpenIddict OWIN authentication handlers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void Configuration(IAppBuilder app)
{
var services = new ServiceCollection();

// ...

var container = services.BuildServiceProvider();

app.Use(async (context, next) =>
{
var scope = container.CreateScope();

// Store the per-request service provider in the OWIN environment.
context.Set(typeof(IServiceProvider).FullName, scope.ServiceProvider);

try
{
// Invoke the rest of the pipeline.
await next();
}

finally
{
// Remove the scoped service provider from the OWIN environment.
context.Set<IServiceProvider>(typeof(IServiceProvider).FullName, null);

// Dispose of the scoped service provider.
if (scope is IAsyncDisposable disposable)
{
await disposable.DisposeAsync();
}

else
{
scope.Dispose();
}
}
});

app.UseOpenIddictServer();
app.UseOpenIddictValidation();
}

To ensure things are working properly, you can use Postman and send a token request:

Configure ASP.NET Web API to use token authentication (optional)

If your application uses ASP.NET Web API, a couple additional things will need to be configured:

  • The OWIN host MUST be used to register the ASP.NET Web API integration. Concretely, this means you must ensure your application doesn't reference the Microsoft.AspNet.WebApi.WebHost package. Instead, use the Microsoft.AspNet.WebApi.Owin package and call app.UseWebApi() at the end of your Startup.Configuration(IAppBuilder app) method:
1
2
3
4
5
6
7
8
9
public void Configuration(IAppBuilder app)
{
// ...

var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();

app.UseWebApi(configuration);
}

When using Autofac and its ASP.NET Web API integration, you'll also need to call app.UseAutofacWebApi(configuration) before app.UseWebApi(configuration). For more information, read the Autofac documentation.

  • To use token authentication for all Web API actions, the HostAuthenticationFilter can be registered globally to always call the OpenIddict validation handler:
1
2
3
4
5
6
7
8
9
10
11
12
public void Configuration(IAppBuilder app)
{
// ...

var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();

// Configure ASP.NET Web API to use token authentication.
configuration.Filters.Add(new HostAuthenticationFilter(OpenIddictValidationOwinDefaults.AuthenticationType));

app.UseWebApi(configuration);
}
  • Alternatively, HostAuthenticationFilter can be applied per-action or per-controller using HostAuthenticationAttribute:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[HostAuthentication(OpenIddictValidationOwinDefaults.AuthenticationType)]
[RoutePrefix("api")]
public class ResourceController : ApiController
{
[Authorize, HttpGet]
[Route("message")]
public IHttpActionResult GetMessage()
{
var principal = (ClaimsPrincipal) User;
var name = principal.FindFirst(Claims.Name).Value;

return Content(HttpStatusCode.OK, $"{name} has been successfully authenticated.");
}
}