OpenIddict RC2 is now on NuGet.org

Earlier today, new OpenIddict packages were pushed to NuGet.org:

What's new?

Starting with RC2, using OpenIddict with third-party client applications (i.e applications you don't own and are managed by someone else) is officially supported. For that, new features – that were still work in progress in the previous iterations – have been added:

  • A new application permissions feature was added, which allows controlling and limiting the features a client application can use. For instance, to allow a client application to use only the authorization code flow and the logout endpoint, the following permissions can be granted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
DisplayName = "MVC client application",
PostLogoutRedirectUris = { new Uri("http://localhost:53507/signout-callback-oidc") },
RedirectUris = { new Uri("http://localhost:53507/signin-oidc") },
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Logout,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode
}
});

For more information about this feature, you can read the corresponding documentation.

  • A new opt-in scope validation option was added. When it is enabled, OpenIddict automatically rejects authorization and token requests that specify unregistered scopes. Scopes can be registered statically using options.RegisterScopes([list of authorized scopes]) or dynamically, using OpenIddictScopeManager.CreateAsync():
1
2
3
4
5
6
7
8
9
10
11
12
services.AddOpenIddict(options =>
{
// ...
// Mark the "email" and "profile" scopes as valid scopes.
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile);
// Enable scope validation, so that authorization and token requests
// that specify unregistered scopes are automatically rejected.
options.EnableScopeValidation();
});
1
2
3
4
5
6
await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
{
Description = "Grants access to the reporting API",
DisplayName = "Reporting API",
Name = "reporting"
});
  • The introspection endpoint was updated to reject access tokens that don't have any audience (since OpenIddict can no longer assume all the registered applications are fully trusted). This change requires updating your code to explicitly attach a resource to your tokens.
1
2
3
4
5
6
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources("reporting");

Read more

Why you should never use Html.Raw in your Razor views

Earlier today, Microsoft released two security advisories for vulnerabilities I discovered in the ASP.NET Core 2.0 VS2017 templates and reported late October:

While the first one is a very classic case of cross-site request forgery (CSRF), the second one is a bit more interesting as it relies on a specificity of ASP.NET Core MVC to be exploited.

Where was the vulnerability located?

When you create a new project based on the ASP.NET Core 2.0 templates offered by Visual Studio 2017 and opt for individual authentication (that uses ASP.NET Core Identity under the hood), a new ManageController and a bunch of views are automatically added to the resulting solution.

One of the actions exposed by this controller, EnableAuthenticator, allows you to generate a shared secret you can use in your favorite TOTP-based application (like Microsoft or Google Authenticator) to enable 2-factor authentication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[HttpGet]
public async Task<IActionResult> EnableAuthenticator()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
var model = new EnableAuthenticatorViewModel
{
SharedKey = FormatKey(unformattedKey),
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
};
return View(model);
}

For that, a random value is generated server-side by Identity, stored in the database and used to build a special otpauth://totp URI which is rendered in the Razor view as an HTML attribute (that can be optionally read by a JS library to generate a QR code image client-side):

1
2
3
4
5
6
<li>
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Html.Raw(Model.AuthenticatorUri)"></div>
</li>
1
2
3
4
5
6
<li>
<p>Scan the QR Code or enter this key <kbd>xuht bbzd 3juv 4kt6 glpb l5tc jbc6 yjsn</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="otpauth://totp/myappname:alice@bob.com?secret=XUHTBBZD3JUV4KT6GLPBL5TCJBC6YJSN&issuer=myappname&digits=6"></div>
</li>

What does the vulnerability consist in?

As you can see, the view uses Html.Raw to render the generated authenticator URI, which is confirmed by the fact the & character is not properly HTML-encoded (it should be rendered in the HTML document as &amp; since it's a special character).

Innocently, you might think it's not a big deal since AuthenticatorUri is generated server-side (and thus can't be directly set by an attacker).

Unfortunately, that's not exact: while it's true that adding an AuthenticatorUri paramater will have initially no effect on the GET EnableAuthenticator action (since the value will be always overridden when setting EnableAuthenticatorViewModel.AuthenticatorUri before returning the view), the query string value will be used if the form is re-displayed.

Yet, that's exactly what the POST action does if the model state is not valid (e.g because the 2FA confirmation code was not correctly typed by the user or was invalid, which may happen if the date/time differ between the server and the device that generated the 2FA code):

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
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
// Strip spaces and hypens
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2faTokenValid)
{
ModelState.AddModelError("model.Code", "Verification code is invalid.");
return View(model);
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
return RedirectToAction(nameof(GenerateRecoveryCodes));
}

This feature – which is part of ASP.NET Core MVC's model binding/validation stack – is extremely useful since it's what allows the users of your websites to avoid re-typing all the values of an invalid form when it's re-displayed.

The bad news is that using this specificity alongside Html.Raw can result in a XSS vulnerability being exploitable since an attacker can craft a special URL containing a malicious JavaScript payload that will be executed by the victim's browser if he or she sends an invalid 2FA confirmation code.

For instance, if a victim visits https://localhost:44370/Manage/EnableAuthenticator?AuthenticatorUri=%22%3E%3C/div%3E%00%00%00%00%00%00%00%3Cscript%3Ealert(%22XSS%22)%3C/script%3E (which uses a special pattern to bypass Chrome 61's XSS auditor feature) and enters an invalid code, the following HTML source will be rendered:

1
2
3
4
5
6
<li>
<p>Scan the QR Code or enter this key <kbd></kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url=""></div> <script>alert("XSS")</script>"></div>
</li>

How can I fix the vulnerability?

Detailed instructions are listed on Microsoft's announcement.

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