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:
parent
3e7d1a7fd4
commit
e34a5f8fb8
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue