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)
|
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
|
// 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.
|
// 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
|
// 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();
|
base.Validate();
|
||||||
|
|
||||||
|
if (MaxAge != null && MaxAge.Value < TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("MaxAge must not be a negative TimeSpan.");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ClientId))
|
if (string.IsNullOrEmpty(ClientId))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Options.ClientId must be provided", nameof(ClientId));
|
throw new ArgumentException("Options.ClientId must be provided", nameof(ClientId));
|
||||||
|
|
@ -159,6 +164,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
set => base.Events = value;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used to ensure that the 'id_token' received
|
/// 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
|
/// 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));
|
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);
|
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)
|
private TestServer BuildTestServer(Action<OpenIdConnectOptions> options)
|
||||||
{
|
{
|
||||||
var builder = new WebHostBuilder()
|
var builder = new WebHostBuilder()
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.TestHost;
|
using Microsoft.AspNetCore.TestHost;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -197,6 +199,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
|
||||||
case OpenIdConnectParameterNames.PostLogoutRedirectUri:
|
case OpenIdConnectParameterNames.PostLogoutRedirectUri:
|
||||||
ValidatePostLogoutRedirectUri(actualValues, errors, htmlEncoded);
|
ValidatePostLogoutRedirectUri(actualValues, errors, htmlEncoded);
|
||||||
break;
|
break;
|
||||||
|
case OpenIdConnectParameterNames.MaxAge:
|
||||||
|
ValidateMaxAge(actualValues, errors, htmlEncoded);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unknown parameter \"{paramToValidate}\".");
|
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) =>
|
private void ValidatePostLogoutRedirectUri(IDictionary<string, string> actualParams, ICollection<string> errors, bool htmlEncoded) =>
|
||||||
ValidateParameter(OpenIdConnectParameterNames.PostLogoutRedirectUri, "https://example.com/signout-callback-oidc", actualParams, errors, 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(
|
private void ValidateParameter(
|
||||||
string parameterName,
|
string parameterName,
|
||||||
string expectedValue,
|
string expectedValue,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue