Add POST support for OpenID Connect authorization and logout requests
This commit is contained in:
parent
fa39144937
commit
d9b3ea2a54
|
|
@ -31,11 +31,11 @@ namespace OpenIdConnectSample
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseOpenIdConnectAuthentication(options =>
|
app.UseOpenIdConnectAuthentication(options =>
|
||||||
{
|
{
|
||||||
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
||||||
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
||||||
options.RedirectUri = "http://localhost:42023";
|
options.RedirectUri = "http://localhost:42023";
|
||||||
});
|
});
|
||||||
|
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Authentication;
|
using Microsoft.AspNet.Http.Authentication;
|
||||||
|
|
@ -19,6 +20,7 @@ using Microsoft.Framework.Caching.Distributed;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
|
|
@ -30,6 +32,22 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
{
|
{
|
||||||
private const string NonceProperty = "N";
|
private const string NonceProperty = "N";
|
||||||
private const string UriSchemeDelimiter = "://";
|
private const string UriSchemeDelimiter = "://";
|
||||||
|
|
||||||
|
private const string InputTagFormat = @"<input type=""hidden"" name=""{0}"" value=""{1}"" />";
|
||||||
|
private const string HtmlFormFormat = @"<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Please wait while you're being redirected to the identity provider</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form name=""form"" method=""post"" action=""{0}"">
|
||||||
|
{1}
|
||||||
|
<noscript>Click here to finish the process: <input type=""submit"" /></noscript>
|
||||||
|
</form>
|
||||||
|
<script>document.form.submit();</script>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
private OpenIdConnectConfiguration _configuration;
|
private OpenIdConnectConfiguration _configuration;
|
||||||
|
|
||||||
protected HttpClient Backchannel { get; private set; }
|
protected HttpClient Backchannel { get; private set; }
|
||||||
|
|
@ -93,13 +111,43 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
message = redirectToIdentityProviderNotification.ProtocolMessage;
|
message = redirectToIdentityProviderNotification.ProtocolMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
var redirectUri = message.CreateLogoutRequestUrl();
|
if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.RedirectGet)
|
||||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
|
||||||
{
|
{
|
||||||
Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
|
var redirectUri = message.CreateLogoutRequestUrl();
|
||||||
}
|
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
Response.Redirect(redirectUri);
|
Response.Redirect(redirectUri);
|
||||||
|
}
|
||||||
|
else if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.FormPost)
|
||||||
|
{
|
||||||
|
var inputs = new StringBuilder();
|
||||||
|
foreach (var parameter in message.Parameters)
|
||||||
|
{
|
||||||
|
var name = Options.HtmlEncoder.HtmlEncode(parameter.Key);
|
||||||
|
var value = Options.HtmlEncoder.HtmlEncode(parameter.Value);
|
||||||
|
|
||||||
|
var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
|
||||||
|
inputs.AppendLine(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
var issuer = Options.HtmlEncoder.HtmlEncode(message.IssuerAddress);
|
||||||
|
|
||||||
|
var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
Response.ContentLength = buffer.Length;
|
||||||
|
Response.ContentType = "text/html;charset=UTF-8";
|
||||||
|
|
||||||
|
// Emit Cache-Control=no-cache to prevent client caching.
|
||||||
|
Response.Headers.Set(HeaderNames.CacheControl, "no-cache");
|
||||||
|
Response.Headers.Set(HeaderNames.Pragma, "no-cache");
|
||||||
|
Response.Headers.Set(HeaderNames.Expires, "-1");
|
||||||
|
|
||||||
|
await Response.Body.WriteAsync(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,14 +266,50 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
|
|
||||||
message.State = Options.StateDataFormat.Protect(properties);
|
message.State = Options.StateDataFormat.Protect(properties);
|
||||||
|
|
||||||
var redirectUri = message.CreateAuthenticationRequestUrl();
|
if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.RedirectGet)
|
||||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
|
||||||
{
|
{
|
||||||
Logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
|
var redirectUri = message.CreateAuthenticationRequestUrl();
|
||||||
|
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
Logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response.Redirect(redirectUri);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.FormPost)
|
||||||
|
{
|
||||||
|
var inputs = new StringBuilder();
|
||||||
|
foreach (var parameter in message.Parameters)
|
||||||
|
{
|
||||||
|
var name = Options.HtmlEncoder.HtmlEncode(parameter.Key);
|
||||||
|
var value = Options.HtmlEncoder.HtmlEncode(parameter.Value);
|
||||||
|
|
||||||
|
var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value);
|
||||||
|
inputs.AppendLine(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
var issuer = Options.HtmlEncoder.HtmlEncode(message.IssuerAddress);
|
||||||
|
|
||||||
|
var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs);
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
Response.ContentLength = buffer.Length;
|
||||||
|
Response.ContentType = "text/html;charset=UTF-8";
|
||||||
|
|
||||||
|
// Emit Cache-Control=no-cache to prevent client caching.
|
||||||
|
Response.Headers.Set(HeaderNames.CacheControl, "no-cache");
|
||||||
|
Response.Headers.Set(HeaderNames.Pragma, "no-cache");
|
||||||
|
Response.Headers.Set(HeaderNames.Expires, "-1");
|
||||||
|
|
||||||
|
await Response.Body.WriteAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.Redirect(redirectUri);
|
Logger.LogError("An unsupported authentication method has been configured: {0}", Options.AuthenticationMethod);
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lists the different authentication methods used to
|
||||||
|
/// redirect the user agent to the identity provider.
|
||||||
|
/// </summary>
|
||||||
|
public enum OpenIdConnectAuthenticationMethod
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Emits a 302 response to redirect the user agent to
|
||||||
|
/// the OpenID Connect provider using a GET request.
|
||||||
|
/// </summary>
|
||||||
|
RedirectGet = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits an HTML form to redirect the user agent to
|
||||||
|
/// the OpenID Connect provider using a POST request.
|
||||||
|
/// </summary>
|
||||||
|
FormPost = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
Options.SignInScheme = sharedOptions.Options.SignInScheme;
|
Options.SignInScheme = sharedOptions.Options.SignInScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Options.HtmlEncoder == null)
|
||||||
|
{
|
||||||
|
Options.HtmlEncoder = services.GetHtmlEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
if (Options.StateDataFormat == null)
|
if (Options.StateDataFormat == null)
|
||||||
{
|
{
|
||||||
var dataProtector = dataProtectionProvider.CreateProtector(
|
var dataProtector = dataProtectionProvider.CreateProtector(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Security.Claims;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Authentication;
|
using Microsoft.AspNet.Http.Authentication;
|
||||||
using Microsoft.Framework.Caching.Distributed;
|
using Microsoft.Framework.Caching.Distributed;
|
||||||
|
using Microsoft.Framework.WebEncoders;
|
||||||
using Microsoft.IdentityModel.Protocols;
|
using Microsoft.IdentityModel.Protocols;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
|
||||||
|
|
@ -190,6 +191,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RefreshOnIssuerKeyNotFound { get; set; } = true;
|
public bool RefreshOnIssuerKeyNotFound { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the method used to redirect the user agent to the identity provider.
|
||||||
|
/// </summary>
|
||||||
|
public OpenIdConnectAuthenticationMethod AuthenticationMethod { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the 'resource'.
|
/// Gets or sets the 'resource'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -242,5 +248,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseTokenLifetime { get; set; } = true;
|
public bool UseTokenLifetime { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="IHtmlEncoder"/> used to sanitize HTML outputs.
|
||||||
|
/// </summary>
|
||||||
|
public IHtmlEncoder HtmlEncoder { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,22 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
|
||||||
const string Signin = "/signin";
|
const string Signin = "/signin";
|
||||||
const string Signout = "/signout";
|
const string Signout = "/signout";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChallengeWillIssueHtmlFormWhenEnabled()
|
||||||
|
{
|
||||||
|
var server = CreateServer(options =>
|
||||||
|
{
|
||||||
|
options.Authority = DefaultAuthority;
|
||||||
|
options.ClientId = "Test Id";
|
||||||
|
options.Configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
|
||||||
|
options.AuthenticationMethod = OpenIdConnectAuthenticationMethod.FormPost;
|
||||||
|
});
|
||||||
|
var transaction = await SendAsync(server, DefaultHost + Challenge);
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||||
|
transaction.Response.Content.Headers.ContentType.MediaType.ShouldBe("text/html");
|
||||||
|
transaction.ResponseText.ShouldContain("form");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ChallengeWillSetDefaults()
|
public async Task ChallengeWillSetDefaults()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue