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 =>
|
||||
{
|
||||
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
||||
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
||||
options.RedirectUri = "http://localhost:42023";
|
||||
});
|
||||
{
|
||||
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
||||
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
||||
options.RedirectUri = "http://localhost:42023";
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
|
@ -19,6 +20,7 @@ using Microsoft.Framework.Caching.Distributed;
|
|||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||
|
|
@ -30,6 +32,22 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
{
|
||||
private const string NonceProperty = "N";
|
||||
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;
|
||||
|
||||
protected HttpClient Backchannel { get; private set; }
|
||||
|
|
@ -93,13 +111,43 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
message = redirectToIdentityProviderNotification.ProtocolMessage;
|
||||
}
|
||||
|
||||
var redirectUri = message.CreateLogoutRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.RedirectGet)
|
||||
{
|
||||
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);
|
||||
|
||||
var redirectUri = message.CreateAuthenticationRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
if (Options.AuthenticationMethod == OpenIdConnectAuthenticationMethod.RedirectGet)
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
Logger.LogError("An unsupported authentication method has been configured: {0}", Options.AuthenticationMethod);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
if (Options.HtmlEncoder == null)
|
||||
{
|
||||
Options.HtmlEncoder = services.GetHtmlEncoder();
|
||||
}
|
||||
|
||||
if (Options.StateDataFormat == null)
|
||||
{
|
||||
var dataProtector = dataProtectionProvider.CreateProtector(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Security.Claims;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.Framework.Caching.Distributed;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
|
|
@ -190,6 +191,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the 'resource'.
|
||||
/// </summary>
|
||||
|
|
@ -242,5 +248,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// This is enabled by default.
|
||||
/// </summary>
|
||||
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 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]
|
||||
public async Task ChallengeWillSetDefaults()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue