[ApiAuthorization] Small fixes to adjust for the code on the SPA templates (#7090)
* Fix solution file * Fixes the post_logout_redirect_uri parameter on the DefaultClientRequestParametersProvider * Propagate state on ending session autoredirect * Update defaults for local-spa profile to align them with template code * Disable explicit support for WebApplications, it can still be enabled normally by configuring IS and we haven't evaluated the scenario E2E.
This commit is contained in:
parent
3e3441481c
commit
e0b264f1c0
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
||||
|
|
@ -32,10 +32,5 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
/// The application is a native application like a mobile or desktop application.
|
||||
/// </summary>
|
||||
public const string NativeApp = "NativeApp";
|
||||
|
||||
/// <summary>
|
||||
/// The application is a web application.
|
||||
/// </summary>
|
||||
internal const string WebApplication = "WebApplication";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 System;
|
||||
|
|
@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
{
|
||||
internal class ConfigureClients : IConfigureOptions<ApiAuthorizationOptions>
|
||||
{
|
||||
private const string DefaultLocalSPARelativeRedirectUri = "";
|
||||
private const string DefaultLocalSPARelativePostLogoutRedirectUri = "";
|
||||
private const string DefaultLocalSPARelativeRedirectUri = "/authentication/login-callback";
|
||||
private const string DefaultLocalSPARelativePostLogoutRedirectUri = "/authentication/logout-callback";
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ConfigureClients> _logger;
|
||||
|
|
@ -50,9 +50,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
case ApplicationProfiles.SPA:
|
||||
yield return GetSPA(name, definition);
|
||||
break;
|
||||
//case ApplicationProfiles.WebApplication:
|
||||
// yield return GetWebApplication(name, definition);
|
||||
// break;
|
||||
case ApplicationProfiles.IdentityServerSPA:
|
||||
yield return GetLocalSPA(name, definition);
|
||||
break;
|
||||
|
|
@ -66,47 +63,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
}
|
||||
}
|
||||
|
||||
private Client GetWebApplication(string name, ClientDefinition definition)
|
||||
{
|
||||
if (definition.RedirectUri == null ||
|
||||
!Uri.TryCreate(definition.RedirectUri, UriKind.Absolute, out var redirectUri))
|
||||
{
|
||||
throw new InvalidOperationException($"The redirect uri " +
|
||||
$"'{definition.RedirectUri}' for '{name}' is invalid. " +
|
||||
$"The redirect URI must be an absolute url.");
|
||||
}
|
||||
|
||||
if (definition.LogoutUri == null ||
|
||||
!Uri.TryCreate(definition.LogoutUri, UriKind.Absolute, out var postLogouturi))
|
||||
{
|
||||
throw new InvalidOperationException($"The logout uri " +
|
||||
$"'{definition.LogoutUri}' for '{name}' is invalid. " +
|
||||
$"The logout URI must be an absolute url.");
|
||||
}
|
||||
|
||||
if (!string.Equals(
|
||||
redirectUri.GetLeftPart(UriPartial.Authority),
|
||||
postLogouturi.GetLeftPart(UriPartial.Authority),
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException($"The redirect uri and the logout uri " +
|
||||
$"for '{name}' have a different scheme, host or port.");
|
||||
}
|
||||
|
||||
if (definition.ClientSecret == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The configuration for '{name}' does not contain a client secret. " +
|
||||
$"Client secrets are required for web applications.");
|
||||
}
|
||||
|
||||
return ClientBuilder.WebApplication(name)
|
||||
.WithRedirectUri(definition.RedirectUri)
|
||||
.WithLogoutRedirectUri(definition.LogoutUri)
|
||||
.FromConfiguration()
|
||||
.WithClientSecret(definition.ClientSecret)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private Client GetSPA(string name, ClientDefinition definition)
|
||||
{
|
||||
if (definition.RedirectUri == null ||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 System;
|
||||
|
|
@ -12,8 +12,10 @@ using IdentityServer4.Services;
|
|||
using IdentityServer4.Validation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
||||
{
|
||||
|
|
@ -67,7 +69,13 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
await ctx.SignOutAsync();
|
||||
}
|
||||
|
||||
return new RedirectResult(result.ValidatedRequest.PostLogOutUri);
|
||||
var postLogOutUri = result.ValidatedRequest.PostLogOutUri;
|
||||
if (result.ValidatedRequest.State != null)
|
||||
{
|
||||
postLogOutUri = QueryHelpers.AddQueryString(postLogOutUri, OpenIdConnectParameterNames.State, result.ValidatedRequest.State);
|
||||
}
|
||||
|
||||
return new RedirectResult(postLogOutUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 IdentityServer4.Extensions;
|
||||
|
|
@ -43,9 +43,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
case ApplicationProfiles.NativeApp:
|
||||
responseType = "code";
|
||||
break;
|
||||
//case ApplicationProfiles.WebApplication:
|
||||
// responseType = "id_token code";
|
||||
// break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid application type '{type}' for '{clientId}'.");
|
||||
}
|
||||
|
|
@ -55,7 +52,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
["authority"] = authority,
|
||||
["client_id"] = client.ClientId,
|
||||
["redirect_uri"] = UrlFactory.GetAbsoluteUrl(context, client.RedirectUris.First()),
|
||||
["post_logout_redirect_uri"] = UrlFactory.GetAbsoluteUrl(context, client.RedirectUris.First()),
|
||||
["post_logout_redirect_uri"] = UrlFactory.GetAbsoluteUrl(context, client.PostLogoutRedirectUris.First()),
|
||||
["response_type"] = responseType,
|
||||
["scope"] = string.Join(" ", client.AllowedScopes)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 System;
|
||||
|
|
@ -65,21 +65,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
.WithScopes(IdentityServerConstants.StandardScopes.OfflineAccess);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new builder for an externally registered web application.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id for the web application.</param>
|
||||
/// <returns>A <see cref="ClientBuilder"/>.</returns>
|
||||
internal static ClientBuilder WebApplication(string clientId)
|
||||
{
|
||||
var client = CreateClient(clientId);
|
||||
return new ClientBuilder(client)
|
||||
.WithApplicationProfile(ApplicationProfiles.WebApplication)
|
||||
.WithAllowedGrants(GrantTypes.HybridAndClientCredentials)
|
||||
.WithScopes(IdentityServerConstants.StandardScopes.OfflineAccess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ClientBuilder"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 IdentityServer4.Models;
|
||||
|
|
@ -110,17 +110,5 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
configure(app);
|
||||
Add(app.Build());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an externally registered web application..
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id for the web application.</param>
|
||||
/// <param name="configure">The <see cref="Action{ClientBuilder}"/> to configure the web application.</param>
|
||||
public void AddWebApplication(string clientId, Action<ClientBuilder> configure)
|
||||
{
|
||||
var app = ClientBuilder.WebApplication(clientId);
|
||||
configure(app);
|
||||
Add(app.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
||||
|
|
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|||
.Build());
|
||||
|
||||
// Act
|
||||
configureClientScopes.PostConfigure(Extensions.Options.Options.DefaultName, options);
|
||||
configureClientScopes.PostConfigure(Options.DefaultName, options);
|
||||
|
||||
// Assert
|
||||
foreach (var client in options.Clients)
|
||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|||
.Build());
|
||||
|
||||
// Act
|
||||
configureClientScopes.PostConfigure(Extensions.Options.Options.DefaultName, options);
|
||||
configureClientScopes.PostConfigure(Options.DefaultName, options);
|
||||
|
||||
// Assert
|
||||
var spaClient = Assert.Single(options.Clients, c => c.ClientId == "TestSPA");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 IdentityServer4;
|
||||
|
|
@ -58,13 +58,12 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|||
Assert.Equal("MyClient", client.ClientId);
|
||||
Assert.Equal("MyClient", client.ClientName);
|
||||
Assert.True(client.AllowAccessTokensViaBrowser);
|
||||
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "/authentication/login-callback" }, client.RedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "/authentication/logout-callback" }, client.PostLogoutRedirectUris.ToArray());
|
||||
Assert.Empty(client.AllowedCorsOrigins);
|
||||
Assert.False(client.RequireConsent);
|
||||
Assert.Empty(client.ClientSecrets);
|
||||
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
|
||||
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -101,7 +100,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|||
Assert.Equal(GrantTypes.Code.ToArray(), client.AllowedGrantTypes.ToArray());
|
||||
Assert.True(client.RequirePkce);
|
||||
Assert.False(client.AllowPlainTextPkce);
|
||||
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -142,43 +140,6 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
|
|||
Assert.False(client.RequireConsent);
|
||||
Assert.Empty(client.ClientSecrets);
|
||||
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
|
||||
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetClients_ReadsWebAppFromConfiguration()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["MyClient:Profile"] = "IdentityServerSPA"
|
||||
}).Build();
|
||||
|
||||
var resources = Array.Empty<ApiResource>();
|
||||
var expectedScopes = new[]
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile
|
||||
};
|
||||
|
||||
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
|
||||
|
||||
// Act
|
||||
var clients = clientLoader.GetClients();
|
||||
|
||||
// Assert
|
||||
var client = Assert.Single(clients);
|
||||
Assert.Equal("MyClient", client.ClientId);
|
||||
Assert.Equal("MyClient", client.ClientName);
|
||||
Assert.True(client.AllowAccessTokensViaBrowser);
|
||||
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
|
||||
Assert.Empty(client.AllowedCorsOrigins);
|
||||
Assert.False(client.RequireConsent);
|
||||
Assert.Empty(client.ClientSecrets);
|
||||
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
|
||||
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -209,8 +170,8 @@ var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<str
|
|||
Assert.Equal("MyClient", client.ClientId);
|
||||
Assert.Equal("MyClient", client.ClientName);
|
||||
Assert.True(client.AllowAccessTokensViaBrowser);
|
||||
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "/authentication/login-callback" }, client.RedirectUris.ToArray());
|
||||
Assert.Equal(new[] { "/authentication/logout-callback" }, client.PostLogoutRedirectUris.ToArray());
|
||||
Assert.Empty(client.AllowedCorsOrigins);
|
||||
Assert.False(client.RequireConsent);
|
||||
Assert.Empty(client.ClientSecrets);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -11,9 +12,11 @@ using IdentityServer4.Services;
|
|||
using IdentityServer4.Validation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -79,7 +82,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
ValidatedRequest = new ValidatedEndSessionRequest()
|
||||
{
|
||||
Client = ClientBuilder.IdentityServerSPA("MySPA").Build(),
|
||||
PostLogOutUri = "https://www.example.com/logout"
|
||||
PostLogOutUri = "https://www.example.com/logout",
|
||||
State = "appState"
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -99,10 +103,11 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
// Assert
|
||||
Assert.NotNull(response);
|
||||
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
|
||||
Assert.Equal("https://www.example.com/logout", redirect.Url);
|
||||
Assert.Equal("https://www.example.com/logout?state=appState", redirect.Url);
|
||||
|
||||
await response.ExecuteAsync(ctx);
|
||||
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
|
||||
Assert.Equal("https://www.example.com/logout", ctx.Response.Headers[HeaderNames.Location]);
|
||||
Assert.Equal("https://www.example.com/logout?state=appState", ctx.Response.Headers[HeaderNames.Location]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
// 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 System;
|
||||
using System.Collections.Generic;
|
||||
using IdentityServer4.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Extensions
|
||||
{
|
||||
public class DefaultClientRequestParametersProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetClientParameters_ReturnsParametersForExistingClients()
|
||||
{
|
||||
// Arrange
|
||||
var absoluteUrlFactory = new Mock<IAbsoluteUrlFactory>();
|
||||
absoluteUrlFactory.Setup(auf => auf.GetAbsoluteUrl(It.IsAny<HttpContext>(), It.IsAny<string>()))
|
||||
.Returns<HttpContext, string>((_, s) => Uri.IsWellFormedUriString(s, UriKind.Absolute) ? s : new Uri(new Uri("http://localhost/"), s).ToString());
|
||||
|
||||
var options = Options.Create(new ApiAuthorizationOptions());
|
||||
options.Value.Clients.AddIdentityServerSPA("SPA", cb =>
|
||||
cb.WithScopes("a/b", "c/d")
|
||||
.WithRedirectUri("authentication/login-callback")
|
||||
.WithLogoutRedirectUri("authentication/logout-callback"));
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Scheme = "http";
|
||||
context.Request.Host = new HostString("localhost");
|
||||
context.RequestServices = new ServiceCollection()
|
||||
.AddSingleton(new IdentityServerOptions())
|
||||
.BuildServiceProvider();
|
||||
|
||||
var clientRequestParametersProvider =
|
||||
new DefaultClientRequestParametersProvider(
|
||||
absoluteUrlFactory.Object,
|
||||
options);
|
||||
|
||||
var expectedParameters = new Dictionary<string, string>
|
||||
{
|
||||
["authority"] = "http://localhost",
|
||||
["client_id"] = "SPA",
|
||||
["redirect_uri"] = "http://localhost/authentication/login-callback",
|
||||
["post_logout_redirect_uri"] = "http://localhost/authentication/logout-callback",
|
||||
["response_type"] = "id_token token",
|
||||
["scope"] = "a/b c/d"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = clientRequestParametersProvider.GetClientParameters(context, "SPA");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedParameters, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue