Add a 'sid' check to the OIDC middleware to prevent unsolicited logout when possible
This commit is contained in:
parent
e299695974
commit
56dca7e0bc
|
|
@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Builder;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -42,6 +41,7 @@ namespace OpenIdConnectSample
|
|||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
|
||||
{
|
||||
loggerfactory.AddConsole(LogLevel.Information);
|
||||
loggerfactory.AddDebug(LogLevel.Information);
|
||||
|
||||
// Simple error page
|
||||
app.Use(async (context, next) =>
|
||||
|
|
@ -98,11 +98,11 @@ namespace OpenIdConnectSample
|
|||
if (context.Request.Path.Equals("/signout-remote"))
|
||||
{
|
||||
// Redirects
|
||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await context.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties()
|
||||
{
|
||||
RedirectUri = "/signedout"
|
||||
});
|
||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return;
|
||||
}
|
||||
if (context.Request.Path.Equals("/Account/AccessDenied"))
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-*",
|
||||
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-*",
|
||||
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
|
||||
"Microsoft.Extensions.Logging.Debug": "1.0.0-*",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@
|
|||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||
{
|
||||
public class RemoteSignOutContext : BaseOpenIdConnectContext
|
||||
{
|
||||
public RemoteSignOutContext(HttpContext context, OpenIdConnectOptions options)
|
||||
public RemoteSignOutContext(
|
||||
HttpContext context,
|
||||
OpenIdConnectOptions options,
|
||||
OpenIdConnectMessage message)
|
||||
: base(context, options)
|
||||
{
|
||||
ProtocolMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,8 @@ namespace Microsoft.Extensions.Logging
|
|||
private static Action<ILogger, Exception> _remoteSignOutHandledResponse;
|
||||
private static Action<ILogger, Exception> _remoteSignOutSkipped;
|
||||
private static Action<ILogger, Exception> _remoteSignOut;
|
||||
private static Action<ILogger, Exception> _remoteSignOutSessionIdMissing;
|
||||
private static Action<ILogger, Exception> _remoteSignOutSessionIdInvalid;
|
||||
|
||||
static LoggingExtensions()
|
||||
{
|
||||
|
|
@ -226,6 +228,16 @@ namespace Microsoft.Extensions.Logging
|
|||
eventId: 46,
|
||||
logLevel: LogLevel.Information,
|
||||
formatString: "Remote signout request processed.");
|
||||
_remoteSignOutSessionIdMissing = LoggerMessage.Define(
|
||||
eventId: 47,
|
||||
logLevel: LogLevel.Error,
|
||||
formatString: "The remote signout request was ignored because the 'sid' parameter " +
|
||||
"was missing, which may indicate an unsolicited logout.");
|
||||
_remoteSignOutSessionIdInvalid = LoggerMessage.Define(
|
||||
eventId: 48,
|
||||
logLevel: LogLevel.Error,
|
||||
formatString: "The remote signout request was ignored because the 'sid' parameter didn't match " +
|
||||
"the expected value, which may indicate an unsolicited logout.");
|
||||
}
|
||||
|
||||
public static void UpdatingConfiguration(this ILogger logger)
|
||||
|
|
@ -442,5 +454,15 @@ namespace Microsoft.Extensions.Logging
|
|||
{
|
||||
_remoteSignOut(logger, null);
|
||||
}
|
||||
|
||||
public static void RemoteSignOutSessionIdMissing(this ILogger logger)
|
||||
{
|
||||
_remoteSignOutSessionIdMissing(logger, null);
|
||||
}
|
||||
|
||||
public static void RemoteSignOutSessionIdInvalid(this ILogger logger)
|
||||
{
|
||||
_remoteSignOutSessionIdInvalid(logger, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,29 +66,76 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
{
|
||||
if (Options.RemoteSignOutPath.HasValue && Options.RemoteSignOutPath == Request.Path)
|
||||
{
|
||||
var remoteSignOutContext = new RemoteSignOutContext(Context, Options);
|
||||
await Options.Events.RemoteSignOut(remoteSignOutContext);
|
||||
|
||||
if (remoteSignOutContext.HandledResponse)
|
||||
{
|
||||
Logger.RemoteSignOutHandledResponse();
|
||||
return true;
|
||||
}
|
||||
if (remoteSignOutContext.Skipped)
|
||||
{
|
||||
Logger.RemoteSignOutSkipped();
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.RemoteSignOut();
|
||||
|
||||
// We've received a remote sign-out request
|
||||
await Context.Authentication.SignOutAsync(Options.SignOutScheme ?? Options.SignInScheme);
|
||||
return true;
|
||||
return await HandleRemoteSignOutAsync();
|
||||
}
|
||||
return await base.HandleRequestAsync();
|
||||
}
|
||||
|
||||
protected virtual async Task<bool> HandleRemoteSignOutAsync()
|
||||
{
|
||||
OpenIdConnectMessage message = null;
|
||||
|
||||
if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
message = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
|
||||
}
|
||||
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
|
||||
else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrEmpty(Request.ContentType)
|
||||
// May have media/type; charset=utf-8, allow partial match.
|
||||
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
|
||||
&& Request.Body.CanRead) {
|
||||
var form = await Request.ReadFormAsync();
|
||||
message = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair<string, string[]>(pair.Key, pair.Value)));
|
||||
}
|
||||
|
||||
var remoteSignOutContext = new RemoteSignOutContext(Context, Options, message);
|
||||
await Options.Events.RemoteSignOut(remoteSignOutContext);
|
||||
|
||||
if (remoteSignOutContext.HandledResponse)
|
||||
{
|
||||
Logger.RemoteSignOutHandledResponse();
|
||||
return true;
|
||||
}
|
||||
if (remoteSignOutContext.Skipped)
|
||||
{
|
||||
Logger.RemoteSignOutSkipped();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to extract the session identifier from the authentication ticket persisted by the sign-in handler.
|
||||
// If the identifier cannot be found, bypass the session identifier checks: this may indicate that the
|
||||
// authentication cookie was already cleared, that the session identifier was lost because of a lossy
|
||||
// external/application cookie conversion or that the identity provider doesn't support sessions.
|
||||
var sid = (await Context.Authentication.AuthenticateAsync(Options.SignOutScheme))?.FindFirst("sid")?.Value;
|
||||
if (!string.IsNullOrEmpty(sid))
|
||||
{
|
||||
// Ensure a 'sid' parameter was sent by the identity provider.
|
||||
if (string.IsNullOrEmpty(message.GetParameter("sid")))
|
||||
{
|
||||
Logger.RemoteSignOutSessionIdMissing();
|
||||
return true;
|
||||
}
|
||||
// Ensure the 'sid' parameter corresponds to the 'sid' stored in the authentication ticket.
|
||||
if (!string.Equals(sid, message.GetParameter("sid"), StringComparison.Ordinal))
|
||||
{
|
||||
Logger.RemoteSignOutSessionIdInvalid();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.RemoteSignOut();
|
||||
|
||||
// We've received a remote sign-out request
|
||||
await Context.Authentication.SignOutAsync(Options.SignOutScheme);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles Signout
|
||||
/// </summary>
|
||||
|
|
@ -132,7 +179,9 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
message.PostLogoutRedirectUri = logoutRedirectUri;
|
||||
}
|
||||
|
||||
message.IdTokenHint = await Context.Authentication.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
|
||||
// Attach the identity token to the logout request when possible.
|
||||
message.IdTokenHint = await Context.Authentication.GetTokenAsync(Options.SignOutScheme, OpenIdConnectParameterNames.IdToken);
|
||||
|
||||
var redirectContext = new RedirectContext(Context, Options, properties)
|
||||
{
|
||||
ProtocolMessage = message
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
|||
{
|
||||
throw new ArgumentException("Options.SignInScheme is required.");
|
||||
}
|
||||
if (string.IsNullOrEmpty(Options.SignOutScheme))
|
||||
{
|
||||
Options.SignOutScheme = Options.SignInScheme;
|
||||
}
|
||||
|
||||
HtmlEncoder = htmlEncoder;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue