Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
Add strict JAR mode (#4409)
Browse files Browse the repository at this point in the history
* refactor JwtRequest client to be an interface

* add option for strict JAR validation

* add tests for strict validation
  • Loading branch information
leastprivilege authored May 25, 2020
1 parent 1d58568 commit 6d18e30
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />

<!--our stuff -->
<PackageReference Update="IdentityModel" Version="4.2.0" />
<PackageReference Update="IdentityModel" Version="4.3.0" />

<PackageReference Update="IdentityServer4" Version="$(IdentityServerVersion)" />
<PackageReference Update="IdentityServer4.AspNetIdentity" Version="$(IdentityServerVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System;
using System.Net.Http;
using IdentityServer4;
using IdentityServer4.Configuration;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection
Expand Down Expand Up @@ -398,14 +399,15 @@ public static IHttpClientBuilder AddJwtRequestUriHttpClient(this IIdentityServer
client.Timeout = TimeSpan.FromSeconds(IdentityServerConstants.HttpClients.DefaultTimeoutSeconds);
});
}

builder.Services.AddTransient<JwtRequestUriHttpClient>(s =>
builder.Services.AddTransient<IJwtRequestUriHttpClient, DefaultJwtRequestUriHttpClient>(s =>
{
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(name);
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
var options = s.GetRequiredService<IdentityServerOptions>();
return new JwtRequestUriHttpClient(httpClient, loggerFactory);
return new DefaultJwtRequestUriHttpClient(httpClient, options, loggerFactory);
});

return httpBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class IdentityServerOptions
/// Specifies whether scopes in JWTs are emitted as array or string
/// </summary>
public bool EmitScopesAsSpaceDelimitedStringInJwt { get; set; } = false;

/// <summary>
/// Specifies whether the JWT typ and content-type for JWT secured authorization requests is checked according to IETF spec.
/// This might break older OIDC conformant request objects.
/// </summary>
public bool StrictJarValidation { get; set; } = false;

/// <summary>
/// Gets or sets the endpoint configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,39 @@
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.


using System;
using IdentityServer4.Models;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Configuration;

namespace IdentityServer4.Services
{
/// <summary>
/// Models making HTTP requests for JWTs from the authorize endpoint.
/// Default JwtRequest client
/// </summary>
public class JwtRequestUriHttpClient
public class DefaultJwtRequestUriHttpClient : IJwtRequestUriHttpClient
{
private readonly HttpClient _client;
private readonly ILogger<JwtRequestUriHttpClient> _logger;
private readonly IdentityServerOptions _options;
private readonly ILogger<DefaultJwtRequestUriHttpClient> _logger;

/// <summary>
/// Constructor for DefaultJwtRequestUriHttpClient.
/// ctor
/// </summary>
/// <param name="client"></param>
/// <param name="loggerFactory"></param>
public JwtRequestUriHttpClient(HttpClient client, ILoggerFactory loggerFactory)
/// <param name="client">An HTTP client</param>
/// <param name="loggerFactory">The logger factory</param>
public DefaultJwtRequestUriHttpClient(HttpClient client, IdentityServerOptions options, ILoggerFactory loggerFactory)
{
_client = client;
_logger = loggerFactory.CreateLogger<JwtRequestUriHttpClient>();
_options = options;
_logger = loggerFactory.CreateLogger<DefaultJwtRequestUriHttpClient>();
}

/// <summary>
/// Gets a JWT from the url.
/// </summary>
/// <param name="url"></param>
/// <param name="client"></param>
/// <returns></returns>

/// <inheritdoc />
public async Task<string> GetJwtAsync(string url, Client client)
{
var req = new HttpRequestMessage(HttpMethod.Get, url);
Expand All @@ -42,16 +43,23 @@ public async Task<string> GetJwtAsync(string url, Client client)
var response = await _client.SendAsync(req);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
_logger.LogDebug("Success http response from jwt url {url}", url);
if (_options.StrictJarValidation)
{
if (!string.Equals(response.Content.Headers.ContentType.MediaType,
$"application/{JwtClaimTypes.JwtTypes.AuthorizationRequest}", StringComparison.Ordinal))
{
_logger.LogError("Invalid content type {type} from jwt url {url}", response.Content.Headers.ContentType.MediaType, url);
return null;
}
}

// todo: check for content-type of "application/oauth.authz.req+jwt"?
// this might break OIDC's
_logger.LogDebug("Success http response from jwt url {url}", url);

var json = await response.Content.ReadAsStringAsync();
return json;
}

_logger.LogError("Invalid http status code {status} from jwt url {url}", response.StatusCode, url);

return null;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/IdentityServer4/src/Services/IJwtRequestUriHttpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Threading.Tasks;
using IdentityServer4.Models;

namespace IdentityServer4.Services
{
/// <summary>
/// Models making HTTP requests for JWTs from the authorize endpoint.
/// </summary>
public interface IJwtRequestUriHttpClient
{
/// <summary>
/// Gets a JWT from the url.
/// </summary>
/// <param name="url"></param>
/// <param name="client"></param>
/// <returns></returns>
Task<string> GetJwtAsync(string url, Client client);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class AuthorizeRequestValidator : IAuthorizeRequestValidator
private readonly IResourceValidator _resourceValidator;
private readonly IUserSession _userSession;
private readonly JwtRequestValidator _jwtRequestValidator;
private readonly JwtRequestUriHttpClient _jwtRequestUriHttpClient;
private readonly IJwtRequestUriHttpClient _jwtRequestUriHttpClient;
private readonly ILogger _logger;

private readonly ResponseTypeEqualityComparer
Expand All @@ -41,7 +41,7 @@ public AuthorizeRequestValidator(
IResourceValidator resourceValidator,
IUserSession userSession,
JwtRequestValidator jwtRequestValidator,
JwtRequestUriHttpClient jwtRequestUriHttpClient,
IJwtRequestUriHttpClient jwtRequestUriHttpClient,
ILogger<AuthorizeRequestValidator> logger)
{
_options = options;
Expand Down
17 changes: 14 additions & 3 deletions src/IdentityServer4/src/Validation/Default/JwtRequestValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Configuration;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -56,13 +57,20 @@ protected string AudienceUri
/// The logger
/// </summary>
protected readonly ILogger Logger;

/// <summary>
/// The optione
/// </summary>
protected readonly IdentityServerOptions Options;

/// <summary>
/// Instantiates an instance of private_key_jwt secret validator
/// </summary>
public JwtRequestValidator(IHttpContextAccessor contextAccessor, ILogger<JwtRequestValidator> logger)
public JwtRequestValidator(IHttpContextAccessor contextAccessor, IdentityServerOptions options, ILogger<JwtRequestValidator> logger)
{
_httpContextAccessor = contextAccessor;

Options = options;
Logger = logger;
}

Expand Down Expand Up @@ -169,10 +177,13 @@ protected virtual Task<JwtSecurityToken> ValidateJwtAsync(string jwtTokenString,
RequireExpirationTime = true
};

if (Options.StrictJarValidation)
{
tokenValidationParameters.ValidTypes = new[] { JwtClaimTypes.JwtTypes.AuthorizationRequest };
}

Handler.ValidateToken(jwtTokenString, tokenValidationParameters, out var token);

// todo: add validation for "typ" header

return Task.FromResult((JwtSecurityToken)token);
}

Expand Down
Loading

0 comments on commit 6d18e30

Please sign in to comment.