The new ASOS and OpenIddict packages are now on NuGet.org

Today's the day: new ASOS and OpenIddict packages (compatible with ASP.NET Core 1.0 and 2.0) have just been pushed to NuGet.org:

What's new?

In AspNet.Security.OpenIdConnect.Server and Owin.Security.OpenIdConnect.Server 1.0.2

  • Calling context.HandleResponse() from the SerializeAuthorizationCode, SerializeAccessToken, SerializeIdentityToken and SerializeRefreshToken events no longer throws an exception if the authentication ticket is not explicitly set (c734c6f).
  • An invalid exception message mentioning OpenIddict was reworded (cd83912).
  • The authorization code/access token/identity token/refresh token deserialization methods are no longer called twice for introspection and revocation requests that specify a token_type_hint that doesn't match the actual token type (c561a34).
  • A standard-compliant Expires HTTP header is now returned by the non-interactive ASOS endpoints (5af1d44).
  • New constants have been added to OpenIdConnectConstants (0980fb8) (461ecd4).
  • New events allowing to control the sign-in, sign-out and challenge operations have been introduced (d95810b) (3801427).

In AspNet.Security.OpenIdConnect.Server 2.0.0-rc1-final

OpenIdConnectServerProvider can now be resolved from the DI container

Good news: OpenIdConnectServerProvider can now be used with dependency injection thanks to a huge refactoring of the ASP.NET Core 2.0 authentication stack, that now implements the options-based pattern I recommended.

To use constructor-injected dependencies in your provider, you can ask ASOS to resolve the provider instance at request-time by setting the new OpenIdConnectServerOptions.ProviderType option (which is a wrapper around AuthenticationSchemeOptions.EventsType):

1
2
3
4
5
6
7
8
9
10
11
public class AuthorizationProvider : OpenIdConnectServerProvider
{
private readonly ApplicationContext _database;

public AuthorizationProvider(ApplicationContext database)
{
_database = database;
}

// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register your custom provider in the DI container.
services.AddScoped<AuthorizationProvider>();

services.AddAuthentication()
.AddOpenIdConnectServer(options =>
{
// ...

// Ask ASOS to resolve the provider instance corresponding
// to the specified type when an OIDC request is received.
options.ProviderType = typeof(AuthorizationProvider);
});
}
}

Such feature requires built-in support in the authentication stack and thus, is unfortunately not available in the OWIN/Katana version of ASOS 2.x.

In Owin.Security.OpenIdConnect.Server 2.0.0-rc1-final

The OWIN/Katana version of ASOS 2.x now requires targeting the Microsoft.Owin 4.0.0-alpha1 packages, which are natively compatible with IdentityModel 5.2.0-preview1 (unlike the previous iteration).

In OpenIddict 1.0.0-rc1-final and 2.0.0-rc1-final

OpenIddict 1.0.0-rc1-final/2.0.0-rc1-final is the first public version of OpenIddict. To learn more about the changes added since the first betas, don't hesitate to take a look at the GitHub repository.

Read more

Using Azure Key Vault with ASOS and OpenIddict

Last week, I received an email from someone who was asking me whether ASOS or OpenIddict could be used with Azure Key Vault (Microsoft's cloud-based Hardware Security Module offer). Since it's quite a recurring question, I thought it was worth writing a blog post to demonstrate how to do that.

So, good news: this is definitely possible! And thanks to a new project released recently by Oren Novotny, this has never been so easy.

What is a Hardware Security Module?

A HSM is a hardened device – generally a PCI board or a standalone appliance – that is exclusively dedicated to cryptographic operations (data encryption/decryption, data signing/verification, key management, etc.). Though HSMs are complex systems, the main idea is actually simple: key material should never leave the HSM's secure enclave, which is protected against physical or digital intrusions.

As such, when a service requires encrypting or signing data (e.g a JWT access token in our case), it has to ask the Hardware Security Module to execute the cryptographic operation on its behalf.

Use Azure Key Vault to sign the JWT tokens issued by ASOS/OpenIddict

Create a new HSM key and register a client application allowed to access it

If you don't have a key yet, you'll have to create one, register a new application in the Azure Active Directory database and grant it an access to your signing key. If you're not familiar with this procedure, I encourage you to read this tutorial or watch the corresponding video:

Azure Key Vault actually offers two pricing tiers: standard and premium. To be able to generate a HSM-protected key (referred to as "hardware key"), you must choose the premium tier when creating your vault. Don't worry, the difference between the two tiers is unbelievably marginal: at the time of writing, $1/month per RSA 2048-bit key.

Read more

AspNet.Security.OpenIdConnect.Server 1.0.1 is out

Earlier today, a patch release for the OpenID Connect server middleware (aka ASOS) was pushed to NuGet.org.

Here's the changelog:

  • The MatchEndpointContext.Options property that had been accidentally removed in 1.0.0 has been reintroduced in 1.0.1 (f17d9a4).
  • An exception is now automatically thrown at startup when registering a signing certificate that is no longer or not yet valid (583be00).
  • Internal code relying on JwtSecurityTokenHandler.CreateToken()/WriteToken() has been updated to use JwtSecurityTokenHandler.CreateEncodedJwt() to ensure JWT tokens are not signed twice (a499f11).

To migrate to the 1.0.1 packages, update your project.json/.csproj project file(s) manually or run the following command in the Package Manager Console:

ASP.NET Core 1.x version

1
Update-Package AspNet.Security.OpenIdConnect.Server -Version 1.0.1

OWIN/Katana 3.x version

1
Update-Package Owin.Security.OpenIdConnect.Server -Version 1.0.1

AspNet.Security.OpenIdConnect.Server 1.0.0 general availability

Today is a great day for me, as I just uploaded the ASOS 1.0.0 packages to NuGet.org, concluding a 3-year work on this library. A huge thanks to everyone – clients, sponsors, contributors, users – who helped me make this possible: ASOS wouldn't exist without your support!

Migrating to 1.0.0

Migrating from ASOS RC1 to RTM should be rather smooth as no major change was adopted in this release, but there's an important requirement you must comply with to ensure your application still works correctly after the migration: you must also update the validation/introspection middleware to the 1.0.0 RTM version (the previous beta versions won't deliberately work):

1
2
3
4
5
6
7
8
9
<Project Sdk="Microsoft.NET.Sdk.Web">

<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.Introspection" Version="1.0.0" />
<PackageReference Include="AspNet.Security.OAuth.Validation" Version="1.0.0" />
<PackageReference Include="AspNet.Security.OpenIdConnect.Server" Version="1.0.0" />
</ItemGroup>

</Project>

If you use recent OpenIddict packages (that use ASOS 1.0.0) or if you explicitly downloaded AspNet.Security.OpenIdConnect.Server or AspNet.Security.OAuth.Introspection 1.0.0 from the aspnet-contrib MyGet feed, consider clearing your NuGet packages folder, as the bits published on NuGet.org slightly differ from the packages initially published on MyGet.org (e.g the ASOS package uploaded to NuGet.org offers ECDSA support when running on .NET Framework 4.7).

For that, close your VS instances, go to C:\Users\[username]\.nuget\packages (on Windows) or ~/.nuget/packages (on macOS) and remove the following folders:

Then, re-open VS and restore your projects or run dotnet restore to download the latest binaries from NuGet.org.

Support lifecycle

No software can reasonably ship without a solid support lifecycle and ASOS is no exception. To keep things simple, I've decided to adopt the same support policy as the one used by Microsoft for the .NET Core platform, which means the aspnet-contrib 1.0.0 RTM packages will get critical fixes and security patches until at least July of 2018.

What's next?

The next big step is to port ASOS to the new ASP.NET Core 2.0 authentication stack I helped redesign, as the 1.0 ASOS bits won't work at all due to the massive breaking changes adopted in 2.0 (e.g authentication handlers are now registered in the DI container and a unique authentication middleware handles everything at the pipeline level, in Startup.Configure(IApplicationBuilder app).

Migrating to AspNet.Security.OpenIdConnect.Server RC1

Earlier today, I released the RC1 version of the OpenID Connect server middleware, alongside the other aspnet-contrib packages.

This version – the latest before RTM – includes a few design changes that will directly impact your own code:

The built-in claims mapping feature was removed

Starting with RC1, ASOS no longer includes a built-in claims mapping feature, which means claims like ClaimTypes.NameIdentifier, ClaimTypes.Name or ClaimTypes.Role are no longer mapped to their OpenID Connect/JWT equivalents (sub, name, role).

Concretely, if you have code like that in your authorization provider class, you should update it to use the OpenID Connect claims instead of the legacy claims exposed by the static ClaimTypes class:

1
2
3
4
5
6
7
8
9
10
11
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);

identity.AddClaim(ClaimTypes.NameIdentifier, "[unique identifier]");

identity.AddClaim(ClaimTypes.Name, "Bob",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);

identity.AddClaim(ClaimTypes.Role, "Administrator",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Configure ClaimsIdentity to use the OpenID Connect claims instead of
// the legacy ClaimTypes claims to populate the ClaimsIdentity.Name property
// and determine how roles are resolved when calling ClaimsPrincipal.IsInRole(...).
var identity = new ClaimsIdentity(
OpenIdConnectServerDefaults.AuthenticationScheme,
OpenIdConnectConstants.Claims.Name,
OpenIdConnectConstants.Claims.Role);

identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique identifier]");

identity.AddClaim(OpenIdConnectConstants.Claims.Name, "Bob",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);

identity.AddClaim(OpenIdConnectConstants.Claims.Role, "Administrator",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);

You're actually free to keep using the ClaimTypes claims, but the OpenID Connect server middleware will throw an exception if you don't (at least) add the sub claim:

InvalidOperationException: The authentication ticket was rejected because it doesn't contain the mandatory subject claim.

If you use the JWT bearer middleware, you'll also want to disable its own claims mapping feature and update the token validation parameters to use the dedicated JWT name/role claims:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = "http://localhost:58795/",
Audience = "resource_server",
RequireHttpsMetadata = false,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = OpenIdConnectConstants.Claims.Name,
RoleClaimType = OpenIdConnectConstants.Claims.Role
}
});

The introspection middleware now uses name and role as the default claim types

In the same vein, the introspection middleware was updated to use name and role as the default claim types (instead of ClaimTypes.Name and ClaimTypes.Role):

1
2
3
4
5
6
7
8
9
10
11
app.UseOAuthIntrospection(options =>
{
options.Authority = new Uri("https://openid.yourapp.com/");
options.Audiences.Add("resource_server");
options.ClientId = "resource_server";
options.ClientSecret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd";

// Override the default claim types used by the introspection middleware:
options.NameClaimType = "custom_name_claim";
options.RoleClaimType = "custom_role_claim";
});

The token format was improved

To support multi-valued authentication properties containing spaces, we had to tweak the token format to store these complex properties as JSON strings.

Unfortunately, this change makes old authorization codes, access and refresh tokens incompatible with the new format (and vice versa). In practice, this means that you can't use tokens issued by ASOS RC1 with old versions of the validation middleware (or with the OpenID Connect server middleware itself): such tokens will be automatically rejected.

To make sure everything runs smoothly, migrate to the latest version of the validation middleware (1.0.0-beta1-final).

Registering a signing key is no longer required when using the default access token format

Starting with RC1, ASOS now includes a "degraded mode" that allows you to use it without registering a signing key or a signing certificate (ephemeral or not) if you don't opt for JWT access tokens and don't use the implicit or hybrid flows.

Concretely, using AddEphemeralKey() or AddCertificate() is no longer mandatory if you use non-interactive flows like password or client credentials AND the default access token format.

And voilà, that's all. For the complete changelist, feel free to take a look at the GitHub issues page.


What's next?

No new release candidate is currently planned, which means the next version will be the RTM package.

The next (and last) step is to rework the XML documentation. Depending on how well this work item goes, the RTM bits should be published at the end of the month or in April. If you're willing to contribute to this stask, don't hesitate to ping me.