#423 Support distributed sign-out.
This commit is contained in:
parent
b358f571d6
commit
d6763bd77c
|
|
@ -1,6 +1,8 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
namespace OpenIdConnectSample
|
namespace OpenIdConnectSample
|
||||||
{
|
{
|
||||||
|
|
@ -8,11 +10,13 @@ namespace OpenIdConnectSample
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
|
|
||||||
|
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel(options =>
|
||||||
.UseConfiguration(config)
|
{
|
||||||
|
//Configure SSL
|
||||||
|
var serverCertificate = LoadCertificate();
|
||||||
|
options.UseHttps(serverCertificate);
|
||||||
|
})
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
|
@ -20,5 +24,23 @@ namespace OpenIdConnectSample
|
||||||
|
|
||||||
host.Run();
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:42023",
|
"applicationUrl": "http://localhost:42023",
|
||||||
"sslPort": 0
|
"sslPort": 44318
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "https://localhost:44318/",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|
@ -18,10 +19,10 @@
|
||||||
"OpenIdConnectSample": {
|
"OpenIdConnectSample": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "http://localhost:42023",
|
"launchUrl": "https://localhost:44318/",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_URLS": "https://localhost:44318/",
|
||||||
"ASPNETCORE_SERVER.URLS": "http://localhost:42023"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,14 @@ namespace OpenIdConnectSample
|
||||||
|
|
||||||
app.Run(async context =>
|
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"))
|
if (context.Request.Path.Equals("/signout"))
|
||||||
{
|
{
|
||||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
|
@ -87,6 +95,16 @@ namespace OpenIdConnectSample
|
||||||
await context.Response.WriteAsync($"</body></html>");
|
await context.Response.WriteAsync($"</body></html>");
|
||||||
return;
|
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"))
|
if (context.Request.Path.Equals("/Account/AccessDenied"))
|
||||||
{
|
{
|
||||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
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=\"/restricted\">Restricted</a><br>");
|
||||||
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</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>");
|
await context.Response.WriteAsync($"</body></html>");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -5,7 +5,9 @@
|
||||||
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0-*",
|
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0-*",
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*",
|
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*",
|
||||||
"Microsoft.AspNetCore.Server.Kestrel": "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.Configuration.UserSecrets": "1.0.0-*",
|
||||||
|
"Microsoft.Extensions.FileProviders.Embedded": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
|
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*"
|
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace SocialSample
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
|
|
||||||
|
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseKestrel(options =>
|
.UseKestrel(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +18,6 @@ namespace SocialSample
|
||||||
var serverCertificate = LoadCertificate();
|
var serverCertificate = LoadCertificate();
|
||||||
options.UseHttps(serverCertificate);
|
options.UseHttps(serverCertificate);
|
||||||
})
|
})
|
||||||
.UseConfiguration(config)
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseIISIntegration()
|
.UseIISIntegration()
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task RedirectToIdentityProviderForSignOut(RedirectContext context);
|
Task RedirectToIdentityProviderForSignOut(RedirectContext context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a request is received on the RemoteSignOutPath.
|
||||||
|
/// </summary>
|
||||||
|
Task RemoteSignOut(RemoteSignOutContext context);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
|
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<RedirectContext, Task> OnRedirectToIdentityProviderForSignOut { get; set; } = context => Task.FromResult(0);
|
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>
|
/// <summary>
|
||||||
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
|
/// Invoked after "authorization code" is redeemed for tokens at the token endpoint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -61,6 +66,8 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
|
|
||||||
public virtual Task RedirectToIdentityProviderForSignOut(RedirectContext context) => OnRedirectToIdentityProviderForSignOut(context);
|
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 TokenResponseReceived(TokenResponseReceivedContext context) => OnTokenResponseReceived(context);
|
||||||
|
|
||||||
public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context);
|
public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,9 @@ namespace Microsoft.Extensions.Logging
|
||||||
private static Action<ILogger, string, Exception> _invalidSecurityTokenType;
|
private static Action<ILogger, string, Exception> _invalidSecurityTokenType;
|
||||||
private static Action<ILogger, string, Exception> _unableToValidateIdToken;
|
private static Action<ILogger, string, Exception> _unableToValidateIdToken;
|
||||||
private static Action<ILogger, string, Exception> _postAuthenticationLocalRedirect;
|
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()
|
static LoggingExtensions()
|
||||||
{
|
{
|
||||||
|
|
@ -211,6 +214,18 @@ namespace Microsoft.Extensions.Logging
|
||||||
eventId: 43,
|
eventId: 43,
|
||||||
logLevel: LogLevel.Error,
|
logLevel: LogLevel.Error,
|
||||||
formatString: "Unable to read the 'id_token', no suitable ISecurityTokenValidator was found for: '{IdToken}'.");
|
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)
|
public static void UpdatingConfiguration(this ILogger logger)
|
||||||
|
|
@ -412,5 +427,20 @@ namespace Microsoft.Extensions.Logging
|
||||||
{
|
{
|
||||||
_postAuthenticationLocalRedirect(logger, redirectUri, null);
|
_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,33 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
HtmlEncoder = htmlEncoder;
|
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>
|
/// <summary>
|
||||||
/// Handles Signout
|
/// Handles Signout
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
AutomaticChallenge = true;
|
AutomaticChallenge = true;
|
||||||
DisplayName = OpenIdConnectDefaults.Caption;
|
DisplayName = OpenIdConnectDefaults.Caption;
|
||||||
CallbackPath = new PathString("/signin-oidc");
|
CallbackPath = new PathString("/signin-oidc");
|
||||||
|
RemoteSignOutPath = new PathString("/signout-oidc");
|
||||||
Events = new OpenIdConnectEvents();
|
Events = new OpenIdConnectEvents();
|
||||||
Scope.Add("openid");
|
Scope.Add("openid");
|
||||||
Scope.Add("profile");
|
Scope.Add("profile");
|
||||||
|
|
@ -155,6 +156,17 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<string> Scope { get; } = new HashSet<string>();
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the type used to secure data handled by the middleware.
|
/// Gets or sets the type used to secure data handled by the middleware.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue