#423 Support distributed sign-out.

This commit is contained in:
Chris R 2016-05-25 16:00:57 -07:00
parent b358f571d6
commit d6763bd77c
12 changed files with 150 additions and 12 deletions

View File

@ -1,6 +1,8 @@
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
namespace OpenIdConnectSample
{
@ -8,11 +10,13 @@ namespace OpenIdConnectSample
{
public static void Main(string[] args)
{
var config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
var host = new WebHostBuilder()
.UseKestrel()
.UseConfiguration(config)
.UseKestrel(options =>
{
//Configure SSL
var serverCertificate = LoadCertificate();
options.UseHttps(serverCertificate);
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
@ -20,5 +24,23 @@ namespace OpenIdConnectSample
host.Run();
}
private static X509Certificate2 LoadCertificate()
{
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var embeddedFileProvider = new EmbeddedFileProvider(assembly, "OpenIdConnectSample");
var certificateFileInfo = embeddedFileProvider.GetFileInfo("compiler/resources/cert.pfx");
using (var certificateStream = certificateFileInfo.CreateReadStream())
{
byte[] certificatePayload;
using (var memoryStream = new MemoryStream())
{
certificateStream.CopyTo(memoryStream);
certificatePayload = memoryStream.ToArray();
}
return new X509Certificate2(certificatePayload, "testPassword");
}
}
}
}

View File

@ -4,13 +4,14 @@
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42023",
"sslPort": 0
"sslPort": 44318
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "https://localhost:44318/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -18,10 +19,10 @@
"OpenIdConnectSample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:42023",
"launchUrl": "https://localhost:44318/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_SERVER.URLS": "http://localhost:42023"
"ASPNETCORE_URLS": "https://localhost:44318/",
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}

View File

@ -78,6 +78,14 @@ namespace OpenIdConnectSample
app.Run(async context =>
{
if (context.Request.Path.Equals("/signedout"))
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<html><body>You have been signed out.<br>{Environment.NewLine}");
await context.Response.WriteAsync("<a href=\"/\">Sign In</a>");
await context.Response.WriteAsync($"</body></html>");
return;
}
if (context.Request.Path.Equals("/signout"))
{
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
@ -87,6 +95,16 @@ namespace OpenIdConnectSample
await context.Response.WriteAsync($"</body></html>");
return;
}
if (context.Request.Path.Equals("/signout-remote"))
{
// Redirects
await context.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties()
{
RedirectUri = "/signedout"
});
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
if (context.Request.Path.Equals("/Account/AccessDenied"))
{
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
@ -134,6 +152,7 @@ namespace OpenIdConnectSample
}
await context.Response.WriteAsync("<a href=\"/restricted\">Restricted</a><br>");
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a><br>");
await context.Response.WriteAsync("<a href=\"/signout-remote\">Sign Out Remote</a><br>");
await context.Response.WriteAsync($"</body></html>");
});
}

View File

@ -5,7 +5,9 @@
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0-*",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.0-*",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-*",
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-*",
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*"
},

View File

@ -11,8 +11,6 @@ namespace SocialSample
{
public static void Main(string[] args)
{
var config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
var host = new WebHostBuilder()
.UseKestrel(options =>
{
@ -20,7 +18,6 @@ namespace SocialSample
var serverCertificate = LoadCertificate();
options.UseHttps(serverCertificate);
})
.UseConfiguration(config)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()

View File

@ -35,6 +35,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// </summary>
Task RedirectToIdentityProviderForSignOut(RedirectContext context);
/// <summary>
/// Invoked when a request is received on the RemoteSignOutPath.
/// </summary>
Task RemoteSignOut(RemoteSignOutContext context);
/// <summary>
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
/// </summary>

View File

@ -36,6 +36,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// </summary>
public Func<RedirectContext, Task> OnRedirectToIdentityProviderForSignOut { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked when a request is received on the RemoteSignOutPath.
/// </summary>
public Func<RemoteSignOutContext, Task> OnRemoteSignOut { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
/// </summary>
@ -61,6 +66,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
public virtual Task RedirectToIdentityProviderForSignOut(RedirectContext context) => OnRedirectToIdentityProviderForSignOut(context);
public virtual Task RemoteSignOut(RemoteSignOutContext context) => OnRemoteSignOut(context);
public virtual Task TokenResponseReceived(TokenResponseReceivedContext context) => OnTokenResponseReceived(context);
public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context);

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
{
public class RemoteSignOutContext : BaseOpenIdConnectContext
{
public RemoteSignOutContext(HttpContext context, OpenIdConnectOptions options)
: base(context, options)
{
}
}
}

View File

@ -47,6 +47,9 @@ namespace Microsoft.Extensions.Logging
private static Action<ILogger, string, Exception> _invalidSecurityTokenType;
private static Action<ILogger, string, Exception> _unableToValidateIdToken;
private static Action<ILogger, string, Exception> _postAuthenticationLocalRedirect;
private static Action<ILogger, Exception> _remoteSignOutHandledResponse;
private static Action<ILogger, Exception> _remoteSignOutSkipped;
private static Action<ILogger, Exception> _remoteSignOut;
static LoggingExtensions()
{
@ -211,6 +214,18 @@ namespace Microsoft.Extensions.Logging
eventId: 43,
logLevel: LogLevel.Error,
formatString: "Unable to read the 'id_token', no suitable ISecurityTokenValidator was found for: '{IdToken}'.");
_remoteSignOutHandledResponse = LoggerMessage.Define(
eventId: 44,
logLevel: LogLevel.Debug,
formatString: "RemoteSignOutContext.HandledResponse");
_remoteSignOutSkipped = LoggerMessage.Define(
eventId: 45,
logLevel: LogLevel.Debug,
formatString: "RemoteSignOutContext.Skipped");
_remoteSignOut = LoggerMessage.Define(
eventId: 46,
logLevel: LogLevel.Information,
formatString: "Remote signout request processed.");
}
public static void UpdatingConfiguration(this ILogger logger)
@ -412,5 +427,20 @@ namespace Microsoft.Extensions.Logging
{
_postAuthenticationLocalRedirect(logger, redirectUri, null);
}
public static void RemoteSignOutHandledResponse(this ILogger logger)
{
_remoteSignOutHandledResponse(logger, null);
}
public static void RemoteSignOutSkipped(this ILogger logger)
{
_remoteSignOutSkipped(logger, null);
}
public static void RemoteSignOut(this ILogger logger)
{
_remoteSignOut(logger, null);
}
}
}

View File

@ -62,6 +62,33 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
HtmlEncoder = htmlEncoder;
}
public override async Task<bool> HandleRequestAsync()
{
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 base.HandleRequestAsync();
}
/// <summary>
/// Handles Signout
/// </summary>

View File

@ -50,6 +50,7 @@ namespace Microsoft.AspNetCore.Builder
AutomaticChallenge = true;
DisplayName = OpenIdConnectDefaults.Caption;
CallbackPath = new PathString("/signin-oidc");
RemoteSignOutPath = new PathString("/signout-oidc");
Events = new OpenIdConnectEvents();
Scope.Add("openid");
Scope.Add("profile");
@ -155,6 +156,17 @@ namespace Microsoft.AspNetCore.Builder
/// </summary>
public ICollection<string> Scope { get; } = new HashSet<string>();
/// <summary>
/// Requests received on this path will cause the middleware to invoke SignOut using the SignInScheme.
/// </summary>
public PathString RemoteSignOutPath { get; set; }
/// <summary>
/// The Authentication Scheme to use with SignOut on the SignOutPath. SignInScheme will be used if this
/// is not set.
/// </summary>
public string SignOutScheme { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>