Add MaxAge to OpenIdConnectOptions

- max_age parameter added to the authentication request if MaxAge is not null
 - throws exception if MaxAge is set to a negative value
 - Fractions of seconds are ignored
 - See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for expected behavior

Addresses #1233
This commit is contained in:
OpenIDAuthority 2017-08-16 21:13:23 -07:00 committed by Chris Ross (ASP.NET)
parent 3e7d1a7fd4
commit e34a5f8fb8
5 changed files with 95 additions and 0 deletions

View File

@ -353,6 +353,14 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
Scope = string.Join(" ", Options.Scope)
};
// Add the 'max_age' parameter to the authentication request if MaxAge is not null.
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
if (Options.MaxAge != null)
{
message.MaxAge = Convert.ToInt64(Math.Floor(((TimeSpan)Options.MaxAge).TotalSeconds))
.ToString(CultureInfo.InvariantCulture);
}
// Omitting the response_mode parameter when it already corresponds to the default
// response_mode used for the specified response_type is recommended by the specifications.
// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes

View File

@ -84,6 +84,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
base.Validate();
if (MaxAge != null && MaxAge.Value < TimeSpan.Zero)
{
throw new InvalidOperationException("MaxAge must not be a negative TimeSpan.");
}
if (string.IsNullOrEmpty(ClientId))
{
throw new ArgumentException("Options.ClientId must be provided", nameof(ClientId));
@ -159,6 +164,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
set => base.Events = value;
}
/// <summary>
/// Gets or sets the 'max_age'. If set the 'max_age' parameter will be sent with the authentication request. If the identity
/// provider has not actively authenticated the user within the length of time specified, the user will be prompted to
/// re-authenticate. By default no max_age is specified.
/// </summary>
public TimeSpan? MaxAge { get; set; } = null;
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used to ensure that the 'id_token' received
/// is valid per: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

View File

@ -409,5 +409,46 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync(ChallengeEndpoint));
Assert.Equal("Cannot redirect to the authorization endpoint, the configuration may be missing or invalid.", exception.Message);
}
[Fact]
public async Task Challenge_WithDefaultMaxAge_HasExpectedMaxAgeParam()
{
var settings = new TestSettings(
opt =>
{
opt.ClientId = "Test Id";
opt.Authority = TestServerBuilder.DefaultAuthority;
});
var server = settings.CreateTestServer();
var transaction = await server.SendAsync(ChallengeEndpoint);
var res = transaction.Response;
settings.ValidateChallengeRedirect(
res.Headers.Location,
OpenIdConnectParameterNames.MaxAge);
}
[Fact]
public async Task Challenge_WithSpecificMaxAge_HasExpectedMaxAgeParam()
{
var settings = new TestSettings(
opt =>
{
opt.ClientId = "Test Id";
opt.Authority = TestServerBuilder.DefaultAuthority;
opt.MaxAge = TimeSpan.FromMinutes(20);
});
var server = settings.CreateTestServer();
var transaction = await server.SendAsync(ChallengeEndpoint);
var res = transaction.Response;
settings.ValidateChallengeRedirect(
res.Headers.Location,
OpenIdConnectParameterNames.MaxAge);
}
}
}

View File

@ -115,6 +115,21 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
);
}
[Fact]
public Task ThrowsWhenMaxAgeIsNegative()
{
return TestConfigurationException<InvalidOperationException>(
o =>
{
o.SignInScheme = "TestScheme";
o.ClientId = "Test Id";
o.Authority = TestServerBuilder.DefaultAuthority;
o.MaxAge = TimeSpan.FromSeconds(-1);
},
ex => Assert.Equal("MaxAge must not be a negative TimeSpan.", ex.Message)
);
}
private TestServer BuildTestServer(Action<OpenIdConnectOptions> options)
{
var builder = new WebHostBuilder()

View File

@ -4,12 +4,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Xml.Linq;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
@ -197,6 +199,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
case OpenIdConnectParameterNames.PostLogoutRedirectUri:
ValidatePostLogoutRedirectUri(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.MaxAge:
ValidateMaxAge(actualValues, errors, htmlEncoded);
break;
default:
throw new InvalidOperationException($"Unknown parameter \"{paramToValidate}\".");
}
@ -263,6 +268,20 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
private void ValidatePostLogoutRedirectUri(IDictionary<string, string> actualParams, ICollection<string> errors, bool htmlEncoded) =>
ValidateParameter(OpenIdConnectParameterNames.PostLogoutRedirectUri, "https://example.com/signout-callback-oidc", actualParams, errors, htmlEncoded);
private void ValidateMaxAge(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded)
{
if(_options.MaxAge != null)
{
string expectedMaxAge = Convert.ToInt64(Math.Floor(((TimeSpan)_options.MaxAge).TotalSeconds))
.ToString(CultureInfo.InvariantCulture);
ValidateParameter(OpenIdConnectParameterNames.MaxAge, expectedMaxAge, actualQuery, errors, htmlEncoded);
}
else if(actualQuery.ContainsKey(OpenIdConnectParameterNames.MaxAge))
{
errors.Add($"Parameter {OpenIdConnectParameterNames.MaxAge} is present but it should be absent");
}
}
private void ValidateParameter(
string parameterName,
string expectedValue,