diff --git a/eng/Versions.props b/eng/Versions.props
index 2fed627a089c..e6fc50fbd089 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -11,6 +11,7 @@
0
6
true
+ 7.0.0-preview
@@ -248,15 +249,15 @@
1.1.2-beta1.22531.1
1.1.2-beta1.22531.1
1.0.0-20230414.1
- 6.15.1
- 6.15.1
- 6.15.1
+ $(IdentityModelVersion)
+ $(IdentityModelVersion)
+ $(IdentityModelVersion)
2.2.1
1.0.1
3.0.1
3.0.1
11.1.0
- 6.21.0
+ $(IdentityModelVersion)
5.0.0
5.0.0-alpha.20560.6
5.0.0
diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs
index 3c063b244100..7c53f799c443 100644
--- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs
+++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs
@@ -266,7 +266,7 @@ await WriteHtmlAsync(response, async res =>
// Persist the new acess token
props.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token"));
props.UpdateTokenValue("refresh_token", payload.RootElement.GetString("refresh_token"));
- if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds))
+ if (payload.RootElement.TryGetProperty("expires_in", out var property) && int.TryParse(property.GetString(), out var seconds))
{
var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
@@ -283,7 +283,7 @@ await WriteHtmlAsync(response, async res =>
await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value }));
await res.WriteAsync("
Payload:
");
- await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
");
+ await res.WriteAsync(HtmlEncoder.Default.Encode(payload.RootElement.ToString()).Replace(",", ",
") + "
");
});
}
diff --git a/src/Security/Authentication/OpenIdConnect/src/LoggingExtensions.cs b/src/Security/Authentication/OpenIdConnect/src/LoggingExtensions.cs
index c0cc19bbf12a..79f0d2e63861 100644
--- a/src/Security/Authentication/OpenIdConnect/src/LoggingExtensions.cs
+++ b/src/Security/Authentication/OpenIdConnect/src/LoggingExtensions.cs
@@ -164,4 +164,10 @@ internal static partial class LoggingExtensions
[LoggerMessage(55, LogLevel.Error, "The remote signout request was ignored because the 'iss' parameter didn't match " +
"the expected value, which may indicate an unsolicited logout.", EventName = "RemoteSignOutIssuerInvalid")]
public static partial void RemoteSignOutIssuerInvalid(this ILogger logger);
+
+ [LoggerMessage(56, LogLevel.Error, "Unable to validate the 'id_token', no suitable TokenHandler was found for: '{IdToken}'.", EventName = "UnableToValidateIdTokenFromHandler")]
+ public static partial void UnableToValidateIdTokenFromHandler(this ILogger logger, string idToken);
+
+ [LoggerMessage(57, LogLevel.Error, "The Validated Security Token must be of type JsonWebToken, but instead its type is: '{SecurityTokenType}'", EventName = "InvalidSecurityTokenTypeFromHandler")]
+ public static partial void InvalidSecurityTokenTypeFromHandler(this ILogger logger, string? securityTokenType);
}
diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs
index 1f2c620def33..bba3df86d0ef 100644
--- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs
+++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs
@@ -17,8 +17,10 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
+using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
+using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect;
@@ -649,7 +651,17 @@ protected override async Task HandleRemoteAuthenticateAsync
if (!string.IsNullOrEmpty(authorizationResponse.IdToken))
{
Logger.ReceivedIdToken();
- user = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt);
+
+ if (!Options.UseSecurityTokenValidator)
+ {
+ var tokenValidationResult = await ValidateTokenUsingHandlerAsync(authorizationResponse.IdToken, properties, validationParameters);
+ user = new ClaimsPrincipal(tokenValidationResult.ClaimsIdentity);
+ jwt = JwtSecurityTokenConverter.Convert(tokenValidationResult.SecurityToken as JsonWebToken);
+ }
+ else
+ {
+ user = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt);
+ }
nonce = jwt.Payload.Nonce;
if (!string.IsNullOrEmpty(nonce))
@@ -717,7 +729,19 @@ protected override async Task HandleRemoteAuthenticateAsync
// At least a cursory validation is required on the new IdToken, even if we've already validated the one from the authorization response.
// And we'll want to validate the new JWT in ValidateTokenResponse.
- var tokenEndpointUser = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out var tokenEndpointJwt);
+ ClaimsPrincipal tokenEndpointUser;
+ JwtSecurityToken tokenEndpointJwt;
+
+ if (!Options.UseSecurityTokenValidator)
+ {
+ var tokenValidationResult = await ValidateTokenUsingHandlerAsync(tokenEndpointResponse.IdToken, properties, validationParameters);
+ tokenEndpointUser = new ClaimsPrincipal(tokenValidationResult.ClaimsIdentity);
+ tokenEndpointJwt = JwtSecurityTokenConverter.Convert(tokenValidationResult.SecurityToken as JsonWebToken);
+ }
+ else
+ {
+ tokenEndpointUser = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out tokenEndpointJwt);
+ }
// Avoid reading & deleting the nonce cookie, running the event, etc, if it was already done as part of the authorization response validation.
if (user == null)
@@ -1244,11 +1268,13 @@ private async Task RunAuthenticationFailedEventAsyn
// Note this modifies properties if Options.UseTokenLifetime
private ClaimsPrincipal ValidateToken(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters, out JwtSecurityToken jwt)
{
+#pragma warning disable CS0618 // Type or member is obsolete
if (!Options.SecurityTokenValidator.CanReadToken(idToken))
{
Logger.UnableToReadIdToken(idToken);
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateToken, idToken));
}
+#pragma warning restore CS0618 // Type or member is obsolete
if (_configuration != null)
{
@@ -1259,7 +1285,9 @@ private ClaimsPrincipal ValidateToken(string idToken, AuthenticationProperties p
?? _configuration.SigningKeys;
}
+#pragma warning disable CS0618 // Type or member is obsolete
var principal = Options.SecurityTokenValidator.ValidateToken(idToken, validationParameters, out SecurityToken validatedToken);
+#pragma warning restore CS0618 // Type or member is obsolete
if (validatedToken is JwtSecurityToken validatedJwt)
{
jwt = validatedJwt;
@@ -1294,6 +1322,61 @@ private ClaimsPrincipal ValidateToken(string idToken, AuthenticationProperties p
return principal;
}
+ // Note this modifies properties if Options.UseTokenLifetime
+ private async Task ValidateTokenUsingHandlerAsync(string idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters)
+ {
+ if (Options.ConfigurationManager is BaseConfigurationManager baseConfigurationManager)
+ {
+ validationParameters.ConfigurationManager = baseConfigurationManager;
+ }
+ else if (_configuration != null)
+ {
+ var issuer = new[] { _configuration.Issuer };
+ validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuer) ?? issuer;
+
+ validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
+ ?? _configuration.SigningKeys;
+ }
+
+ var validationResult = await Options.TokenHandler.ValidateTokenAsync(idToken, validationParameters);
+
+ if (validationResult.Exception != null)
+ {
+ throw validationResult.Exception;
+ }
+
+ var validatedToken = validationResult.SecurityToken;
+
+ if (!validationResult.IsValid || validatedToken == null)
+ {
+ Logger.UnableToValidateIdTokenFromHandler(idToken);
+ throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.UnableToValidateTokenFromHandler, idToken));
+ }
+
+ if (validatedToken is not JsonWebToken)
+ {
+ Logger.InvalidSecurityTokenTypeFromHandler(validatedToken?.GetType().ToString());
+ throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.ValidatedSecurityTokenNotJsonWebToken, validatedToken?.GetType()));
+ }
+
+ if (Options.UseTokenLifetime)
+ {
+ var issued = validatedToken.ValidFrom;
+ if (issued != DateTime.MinValue)
+ {
+ properties.IssuedUtc = issued;
+ }
+
+ var expires = validatedToken.ValidTo;
+ if (expires != DateTime.MinValue)
+ {
+ properties.ExpiresUtc = expires;
+ }
+ }
+
+ return validationResult;
+ }
+
///
/// Build a redirect path if the given path is a relative path.
///
diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs
index 7fada340c927..a942658b07b4 100644
--- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs
+++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs
@@ -4,6 +4,7 @@
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Http;
+using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
@@ -17,6 +18,12 @@ public class OpenIdConnectOptions : RemoteAuthenticationOptions
{
private CookieBuilder _nonceCookieBuilder;
private readonly JwtSecurityTokenHandler _defaultHandler = new JwtSecurityTokenHandler();
+ private readonly JsonWebTokenHandler _defaultTokenHandler = new JsonWebTokenHandler
+ {
+ MapInboundClaims = JwtSecurityTokenHandler.DefaultMapInboundClaims
+ };
+
+ private bool _mapInboundClaims = JwtSecurityTokenHandler.DefaultMapInboundClaims;
///
/// Initializes a new
@@ -37,7 +44,10 @@ public OpenIdConnectOptions()
CallbackPath = new PathString("/signin-oidc");
SignedOutCallbackPath = new PathString("/signout-callback-oidc");
RemoteSignOutPath = new PathString("/signout-oidc");
+#pragma warning disable CS0618 // Type or member is obsolete
SecurityTokenValidator = _defaultHandler;
+#pragma warning restore CS0618 // Type or member is obsolete
+ TokenHandler = _defaultTokenHandler;
Events = new OpenIdConnectEvents();
Scope.Add("openid");
@@ -253,8 +263,17 @@ public override void Validate()
///
/// Gets or sets the used to validate identity tokens.
///
+ [Obsolete("SecurityTokenValidator is no longer used by default. Use TokenHandler instead. To continue using SecurityTokenValidator, set UseSecurityTokenValidator to true. See https://aka.ms/aspnetcore8/security-token-changes")]
public ISecurityTokenValidator SecurityTokenValidator { get; set; }
+ ///
+ /// Gets or sets the used to validate identity tokens.
+ ///
+ /// This will be used instead of if is
+ ///
+ ///
+ public TokenHandler TokenHandler { get; set; }
+
///
/// Gets or sets the parameters used to validate identity tokens.
///
@@ -353,14 +372,25 @@ public override CookieOptions Build(HttpContext context, DateTimeOffset expiresF
public TimeSpan RefreshInterval { get; set; } = ConfigurationManager.DefaultRefreshInterval;
///
- /// Gets or sets the property on the default instance of in SecurityTokenValidator, which is used when determining
+ /// Gets or sets the property on the default instance of in SecurityTokenValidator
+ /// and default instance of in TokenHandler, which is used when determining
/// whether or not to map claim types that are extracted when validating a .
/// If this is set to true, the Claim Type is set to the JSON claim 'name' after translating using this mapping. Otherwise, no mapping occurs.
/// The default value is true.
///
public bool MapInboundClaims
{
- get => _defaultHandler.MapInboundClaims;
- set => _defaultHandler.MapInboundClaims = value;
+ get => _mapInboundClaims;
+ set
+ {
+ _mapInboundClaims = value;
+ _defaultHandler.MapInboundClaims = value;
+ _defaultTokenHandler.MapInboundClaims = value;
+ }
}
+
+ ///
+ /// Gets or sets whether to use the or the for validating identity tokens.
+ ///
+ public bool UseSecurityTokenValidator { get; set; }
}
diff --git a/src/Security/Authentication/OpenIdConnect/src/PublicAPI.Unshipped.txt b/src/Security/Authentication/OpenIdConnect/src/PublicAPI.Unshipped.txt
index 8ff5e3305e99..d6dbc14fed1e 100644
--- a/src/Security/Authentication/OpenIdConnect/src/PublicAPI.Unshipped.txt
+++ b/src/Security/Authentication/OpenIdConnect/src/PublicAPI.Unshipped.txt
@@ -1,2 +1,6 @@
#nullable enable
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.OpenIdConnectHandler(Microsoft.Extensions.Options.IOptionsMonitor! options, Microsoft.Extensions.Logging.ILoggerFactory! logger, System.Text.Encodings.Web.HtmlEncoder! htmlEncoder, System.Text.Encodings.Web.UrlEncoder! encoder) -> void
+Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.TokenHandler.get -> Microsoft.IdentityModel.Tokens.TokenHandler!
+Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.TokenHandler.set -> void
+Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.UseSecurityTokenValidator.get -> bool
+Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.UseSecurityTokenValidator.set -> void
diff --git a/src/Security/Authentication/OpenIdConnect/src/Resources.resx b/src/Security/Authentication/OpenIdConnect/src/Resources.resx
index 7f790fef43f5..fec68b579f86 100644
--- a/src/Security/Authentication/OpenIdConnect/src/Resources.resx
+++ b/src/Security/Authentication/OpenIdConnect/src/Resources.resx
@@ -135,4 +135,10 @@
Cannot process the message. Both id_token and code are missing.
+
+ The Validated Security Token must be of type JsonWebToken, but instead its tye is '{0}'.
+
+
+ Unable to validate the 'id_token', no suitable TokenHandler was found for: '{0}'."
+
\ No newline at end of file
diff --git a/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests.cs b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests.cs
index 3da118f7e03e..b54800821e43 100644
--- a/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests.cs
+++ b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests.cs
@@ -16,6 +16,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
+using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
@@ -1285,7 +1286,10 @@ private TestServer CreateServer(OpenIdConnectEvents events, RequestDelegate appC
EndSessionEndpoint = "http://testhost/end"
};
o.StateDataFormat = new TestStateDataFormat();
+#pragma warning disable CS0618 // Type or member is obsolete
o.SecurityTokenValidator = new TestTokenValidator();
+#pragma warning restore CS0618 // Type or member is obsolete
+ o.UseSecurityTokenValidator = true;
o.ProtocolValidator = new TestProtocolValidator();
o.BackchannelHttpHandler = new TestBackchannel();
});
diff --git a/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests_Handler.cs b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests_Handler.cs
new file mode 100644
index 000000000000..7279b02a724a
--- /dev/null
+++ b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectEventTests_Handler.cs
@@ -0,0 +1,1404 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IdentityModel.Tokens.Jwt;
+using System.Net;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Primitives;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect;
+
+public class OpenIdConnectEventTests_Handlers
+{
+ private readonly RequestDelegate AppWritePath = context => context.Response.WriteAsync(context.Request.Path);
+ private readonly RequestDelegate AppNotImpl = context => { throw new NotImplementedException("App"); };
+
+ [Fact]
+ public async Task OnMessageReceived_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ };
+ events.OnMessageReceived = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnMessageReceived_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnMessageReceived = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnMessageReceived_Handled_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ };
+ events.OnMessageReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidated_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidated_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidated_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.HandleResponse();
+ context.Principal = null;
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidated_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.HandleResponse();
+ context.Principal = null;
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthorizationCodeReceived_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ };
+ events.OnAuthorizationCodeReceived = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthorizationCodeReceived_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnAuthorizationCodeReceived = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthorizationCodeReceived_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ };
+ events.OnAuthorizationCodeReceived = context =>
+ {
+ context.HandleResponse();
+ context.Principal = null;
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthorizationCodeReceived_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnAuthorizationCodeReceived = context =>
+ {
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenResponseReceived_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ };
+ events.OnTokenResponseReceived = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenResponseReceived_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnTokenResponseReceived = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenResponseReceived_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ };
+ events.OnTokenResponseReceived = context =>
+ {
+ context.Principal = null;
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenResponseReceived_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnTokenResponseReceived = context =>
+ {
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidatedBackchannel_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidatedBackchannel_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "state=protected_state&code=my_code");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidatedBackchannel_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.Principal = null;
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTokenValidatedBackchannel_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnTokenValidated = context =>
+ {
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnUserInformationReceived_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnUserInformationReceived_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnUserInformationReceived_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ context.Principal = null;
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnUserInformationReceived_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthenticationFailed_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnAuthenticationFailed = context =>
+ {
+ Assert.Equal("TestException", context.Exception.Message);
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthenticationFailed_Fail_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnAuthenticationFailed = context =>
+ {
+ Assert.Equal("TestException", context.Exception.Message);
+ context.Fail("Authentication was aborted from user code.");
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+ });
+
+ Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthenticationFailed_HandledWithoutTicket_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnAuthenticationFailed = context =>
+ {
+ Assert.Equal("TestException", context.Exception.Message);
+ Assert.Null(context.Principal);
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAuthenticationFailed_HandledWithTicket_SkipToTicketReceived()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnAuthenticationFailed = context =>
+ {
+ Assert.Equal("TestException", context.Exception.Message);
+ Assert.Null(context.Principal);
+
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"),
+ new Claim(ClaimTypes.Email, "bob@contoso.com"),
+ new Claim(ClaimsIdentity.DefaultNameClaimType, "bob")
+ };
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
+ context.Success();
+ return Task.FromResult(0);
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAccessDenied_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectAccessDenied = true
+ };
+ events.OnAccessDenied = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnAccessDenied_Handled_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectAccessDenied = true
+ };
+ events.OnAccessDenied = context =>
+ {
+ Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRemoteFailure_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnAuthenticationFailed = context =>
+ {
+ Assert.Equal("TestException", context.Exception.Message);
+ return Task.FromResult(0);
+ };
+ events.OnRemoteFailure = context =>
+ {
+ Assert.Equal("TestException", context.Failure.Message);
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRemoteFailure_Handled_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectAuthenticationFailed = true,
+ ExpectRemoteFailure = true,
+ };
+ events.OnUserInformationReceived = context =>
+ {
+ throw new NotImplementedException("TestException");
+ };
+ events.OnRemoteFailure = context =>
+ {
+ Assert.Equal("TestException", context.Failure.Message);
+ Assert.Equal("testvalue", context.Properties.Items["testkey"]);
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTicketReceived_Skip_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.SkipHandler();
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppWritePath);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnTicketReceived_Handled_NoMoreEventsRun()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectMessageReceived = true,
+ ExpectTokenValidated = true,
+ ExpectAuthorizationCodeReceived = true,
+ ExpectTokenResponseReceived = true,
+ ExpectUserInfoReceived = true,
+ ExpectTicketReceived = true,
+ };
+ events.OnTicketReceived = context =>
+ {
+ context.HandleResponse();
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.FromResult(0);
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var response = await PostAsync(server, "signin-oidc", "id_token=my_id_token&state=protected_state&code=my_code");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRedirectToIdentityProviderForSignOut_Invoked()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRedirectForSignOut = true,
+ };
+ var server = CreateServer(events,
+ context =>
+ {
+ return context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
+ });
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/");
+
+ Assert.Equal(HttpStatusCode.Found, response.StatusCode);
+ Assert.Equal("http://testhost/end", response.Headers.Location.GetLeftPart(UriPartial.Path));
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRedirectToIdentityProviderForSignOut_Handled_RedirectNotInvoked()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRedirectForSignOut = true,
+ };
+ events.OnRedirectToIdentityProviderForSignOut = context =>
+ {
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ context.HandleResponse();
+ return Task.CompletedTask;
+ };
+ var server = CreateServer(events,
+ context =>
+ {
+ return context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
+ });
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Null(response.Headers.Location);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRemoteSignOut_Invoked()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRemoteSignOut = true,
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-oidc");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ events.ValidateExpectations();
+ Assert.True(response.Headers.TryGetValues(HeaderNames.SetCookie, out var values));
+ Assert.True(SetCookieHeaderValue.TryParseStrictList(values.ToList(), out var parsedValues));
+ Assert.Equal(1, parsedValues.Count);
+ Assert.True(StringSegment.IsNullOrEmpty(parsedValues.Single().Value));
+ }
+
+ [Fact]
+ public async Task OnRemoteSignOut_Handled_NoSignout()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRemoteSignOut = true,
+ };
+ events.OnRemoteSignOut = context =>
+ {
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ context.HandleResponse();
+ return Task.CompletedTask;
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-oidc");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ events.ValidateExpectations();
+ Assert.False(response.Headers.TryGetValues(HeaderNames.SetCookie, out var values));
+ }
+
+ [Fact]
+ public async Task OnRemoteSignOut_Skip_NoSignout()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRemoteSignOut = true,
+ };
+ events.OnRemoteSignOut = context =>
+ {
+ context.SkipHandler();
+ return Task.CompletedTask;
+ };
+ var server = CreateServer(events, context =>
+ {
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.CompletedTask;
+ });
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-oidc");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ events.ValidateExpectations();
+ Assert.False(response.Headers.TryGetValues(HeaderNames.SetCookie, out var values));
+ }
+
+ [Fact]
+ public async Task OnRedirectToSignedOutRedirectUri_Invoked()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRedirectToSignedOut = true,
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-callback-oidc?state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Found, response.StatusCode);
+ Assert.Equal("http://testhost/redirect", response.Headers.Location.AbsoluteUri);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRedirectToSignedOutRedirectUri_Handled_NoRedirect()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRedirectToSignedOut = true,
+ };
+ events.OnSignedOutCallbackRedirect = context =>
+ {
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ context.HandleResponse();
+ return Task.CompletedTask;
+ };
+ var server = CreateServer(events, AppNotImpl);
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-callback-oidc?state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Null(response.Headers.Location);
+ events.ValidateExpectations();
+ }
+
+ [Fact]
+ public async Task OnRedirectToSignedOutRedirectUri_Skipped_NoRedirect()
+ {
+ var events = new ExpectedOidcEvents()
+ {
+ ExpectRedirectToSignedOut = true,
+ };
+ events.OnSignedOutCallbackRedirect = context =>
+ {
+ context.SkipHandler();
+ return Task.CompletedTask;
+ };
+ var server = CreateServer(events,
+ context =>
+ {
+ context.Response.StatusCode = StatusCodes.Status202Accepted;
+ return Task.CompletedTask;
+ });
+
+ var client = server.CreateClient();
+ var response = await client.GetAsync("/signout-callback-oidc?state=protected_state");
+
+ Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
+ Assert.Null(response.Headers.Location);
+ events.ValidateExpectations();
+ }
+
+ private class ExpectedOidcEvents : OpenIdConnectEvents
+ {
+ public bool ExpectMessageReceived { get; set; }
+ public bool InvokedMessageReceived { get; set; }
+
+ public bool ExpectTokenValidated { get; set; }
+ public bool InvokedTokenValidated { get; set; }
+
+ public bool ExpectAccessDenied { get; set; }
+ public bool InvokedAccessDenied { get; set; }
+
+ public bool ExpectRemoteFailure { get; set; }
+ public bool InvokedRemoteFailure { get; set; }
+
+ public bool ExpectTicketReceived { get; set; }
+ public bool InvokedTicketReceived { get; set; }
+
+ public bool ExpectAuthorizationCodeReceived { get; set; }
+ public bool InvokedAuthorizationCodeReceived { get; set; }
+
+ public bool ExpectTokenResponseReceived { get; set; }
+ public bool InvokedTokenResponseReceived { get; set; }
+
+ public bool ExpectUserInfoReceived { get; set; }
+ public bool InvokedUserInfoReceived { get; set; }
+
+ public bool ExpectAuthenticationFailed { get; set; }
+ public bool InvokeAuthenticationFailed { get; set; }
+
+ public bool ExpectRedirectForSignOut { get; set; }
+ public bool InvokedRedirectForSignOut { get; set; }
+
+ public bool ExpectRemoteSignOut { get; set; }
+ public bool InvokedRemoteSignOut { get; set; }
+
+ public bool ExpectRedirectToSignedOut { get; set; }
+ public bool InvokedRedirectToSignedOut { get; set; }
+
+ public override Task MessageReceived(MessageReceivedContext context)
+ {
+ InvokedMessageReceived = true;
+ return base.MessageReceived(context);
+ }
+
+ public override Task TokenValidated(TokenValidatedContext context)
+ {
+ InvokedTokenValidated = true;
+ return base.TokenValidated(context);
+ }
+
+ public override Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
+ {
+ InvokedAuthorizationCodeReceived = true;
+ return base.AuthorizationCodeReceived(context);
+ }
+
+ public override Task TokenResponseReceived(TokenResponseReceivedContext context)
+ {
+ InvokedTokenResponseReceived = true;
+ return base.TokenResponseReceived(context);
+ }
+
+ public override Task UserInformationReceived(UserInformationReceivedContext context)
+ {
+ InvokedUserInfoReceived = true;
+ return base.UserInformationReceived(context);
+ }
+
+ public override Task AuthenticationFailed(AuthenticationFailedContext context)
+ {
+ InvokeAuthenticationFailed = true;
+ return base.AuthenticationFailed(context);
+ }
+
+ public override Task TicketReceived(TicketReceivedContext context)
+ {
+ InvokedTicketReceived = true;
+ return base.TicketReceived(context);
+ }
+
+ public override Task AccessDenied(AccessDeniedContext context)
+ {
+ InvokedAccessDenied = true;
+ return base.AccessDenied(context);
+ }
+
+ public override Task RemoteFailure(RemoteFailureContext context)
+ {
+ InvokedRemoteFailure = true;
+ return base.RemoteFailure(context);
+ }
+
+ public override Task RedirectToIdentityProviderForSignOut(RedirectContext context)
+ {
+ InvokedRedirectForSignOut = true;
+ return base.RedirectToIdentityProviderForSignOut(context);
+ }
+
+ public override Task RemoteSignOut(RemoteSignOutContext context)
+ {
+ InvokedRemoteSignOut = true;
+ return base.RemoteSignOut(context);
+ }
+
+ public override Task SignedOutCallbackRedirect(RemoteSignOutContext context)
+ {
+ InvokedRedirectToSignedOut = true;
+ return base.SignedOutCallbackRedirect(context);
+ }
+
+ public void ValidateExpectations()
+ {
+ Assert.Equal(ExpectMessageReceived, InvokedMessageReceived);
+ Assert.Equal(ExpectTokenValidated, InvokedTokenValidated);
+ Assert.Equal(ExpectAuthorizationCodeReceived, InvokedAuthorizationCodeReceived);
+ Assert.Equal(ExpectTokenResponseReceived, InvokedTokenResponseReceived);
+ Assert.Equal(ExpectUserInfoReceived, InvokedUserInfoReceived);
+ Assert.Equal(ExpectAuthenticationFailed, InvokeAuthenticationFailed);
+ Assert.Equal(ExpectTicketReceived, InvokedTicketReceived);
+ Assert.Equal(ExpectAccessDenied, InvokedAccessDenied);
+ Assert.Equal(ExpectRemoteFailure, InvokedRemoteFailure);
+ Assert.Equal(ExpectRedirectForSignOut, InvokedRedirectForSignOut);
+ Assert.Equal(ExpectRemoteSignOut, InvokedRemoteSignOut);
+ Assert.Equal(ExpectRedirectToSignedOut, InvokedRedirectToSignedOut);
+ }
+ }
+
+ private TestServer CreateServer(OpenIdConnectEvents events, RequestDelegate appCode)
+ {
+ var host = new HostBuilder()
+ .ConfigureWebHost(builder =>
+ builder.UseTestServer()
+ .ConfigureServices(services =>
+ {
+ services.AddAuthentication(auth =>
+ {
+ auth.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+ auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
+ })
+ .AddCookie()
+ .AddOpenIdConnect(o =>
+ {
+ o.Events = events;
+ o.ClientId = "ClientId";
+ o.GetClaimsFromUserInfoEndpoint = true;
+ o.Configuration = new OpenIdConnectConfiguration()
+ {
+ TokenEndpoint = "http://testhost/tokens",
+ UserInfoEndpoint = "http://testhost/user",
+ EndSessionEndpoint = "http://testhost/end"
+ };
+ o.StateDataFormat = new TestStateDataFormat();
+ o.UseSecurityTokenValidator = false;
+ o.TokenHandler = new TestTokenHandler();
+ o.ProtocolValidator = new TestProtocolValidator();
+ o.BackchannelHttpHandler = new TestBackchannel();
+ });
+ })
+ .Configure(app =>
+ {
+ app.UseAuthentication();
+ app.Run(appCode);
+ }))
+ .Build();
+
+ host.Start();
+ return host.GetTestServer();
+ }
+
+ private Task PostAsync(TestServer server, string path, string form)
+ {
+ var client = server.CreateClient();
+ var cookie = ".AspNetCore.Correlation.correlationId=N";
+ client.DefaultRequestHeaders.Add("Cookie", cookie);
+ return client.PostAsync("signin-oidc",
+ new StringContent(form, Encoding.ASCII, "application/x-www-form-urlencoded"));
+ }
+
+ private class TestStateDataFormat : ISecureDataFormat
+ {
+ private AuthenticationProperties Data { get; set; }
+
+ public string Protect(AuthenticationProperties data)
+ {
+ return "protected_state";
+ }
+
+ public string Protect(AuthenticationProperties data, string purpose)
+ {
+ throw new NotImplementedException();
+ }
+
+ public AuthenticationProperties Unprotect(string protectedText)
+ {
+ Assert.Equal("protected_state", protectedText);
+ var properties = new AuthenticationProperties(new Dictionary()
+ {
+ { ".xsrf", "correlationId" },
+ { OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, "redirect_uri" },
+ { "testkey", "testvalue" }
+ });
+ properties.RedirectUri = "http://testhost/redirect";
+ return properties;
+ }
+
+ public AuthenticationProperties Unprotect(string protectedText, string purpose)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class TestTokenHandler : TokenHandler
+ {
+ public override Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
+ {
+ Assert.Equal("my_id_token", token);
+ var jwt = new JwtSecurityToken();
+ return Task.FromResult(new TokenValidationResult()
+ {
+ SecurityToken = new JsonWebToken(jwt.EncodedHeader + "." + jwt.EncodedPayload + "."),
+ ClaimsIdentity = new ClaimsIdentity("customAuthType"),
+ IsValid = true
+ });
+ }
+
+ public override SecurityToken ReadToken(string token)
+ {
+ Assert.Equal("my_id_token", token);
+ return new JsonWebToken(token);
+ }
+ }
+
+ private class TestProtocolValidator : OpenIdConnectProtocolValidator
+ {
+ public override void ValidateAuthenticationResponse(OpenIdConnectProtocolValidationContext validationContext)
+ {
+ }
+
+ public override void ValidateTokenResponse(OpenIdConnectProtocolValidationContext validationContext)
+ {
+ }
+
+ public override void ValidateUserInfoResponse(OpenIdConnectProtocolValidationContext validationContext)
+ {
+ }
+ }
+
+ private class TestBackchannel : HttpMessageHandler
+ {
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (string.Equals("/tokens", request.RequestUri.AbsolutePath, StringComparison.Ordinal))
+ {
+ return Task.FromResult(new HttpResponseMessage()
+ {
+ Content =
+ new StringContent("{ \"id_token\": \"my_id_token\", \"access_token\": \"my_access_token\" }", Encoding.ASCII, "application/json")
+ });
+ }
+ if (string.Equals("/user", request.RequestUri.AbsolutePath, StringComparison.Ordinal))
+ {
+ return Task.FromResult(new HttpResponseMessage() { Content = new StringContent("{ }", Encoding.ASCII, "application/json") });
+ }
+
+ throw new NotImplementedException(request.RequestUri.ToString());
+ }
+ }
+}
diff --git a/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectTests.cs b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectTests.cs
index d9c3f04d9420..49977e6c80e9 100644
--- a/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectTests.cs
+++ b/src/Security/Authentication/test/OpenIdConnect/OpenIdConnectTests.cs
@@ -374,7 +374,9 @@ public void MapInboundClaimsDefaultsToTrue()
{
var options = new OpenIdConnectOptions();
Assert.True(options.MapInboundClaims);
+#pragma warning disable CS0618 // Type or member is obsolete
var jwtHandler = options.SecurityTokenValidator as JwtSecurityTokenHandler;
+#pragma warning restore CS0618 // Type or member is obsolete
Assert.NotNull(jwtHandler);
Assert.True(jwtHandler.MapInboundClaims);
}
@@ -385,7 +387,9 @@ public void MapInboundClaimsCanBeSetToFalse()
var options = new OpenIdConnectOptions();
options.MapInboundClaims = false;
Assert.False(options.MapInboundClaims);
+#pragma warning disable CS0618 // Type or member is obsolete
var jwtHandler = options.SecurityTokenValidator as JwtSecurityTokenHandler;
+#pragma warning restore CS0618 // Type or member is obsolete
Assert.NotNull(jwtHandler);
Assert.False(jwtHandler.MapInboundClaims);
}