diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts b/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts index cff0ff9d12..bf512733d4 100644 --- a/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts @@ -27,7 +27,7 @@ enum AuthenticationResultStatus { Redirect = "redirect", Success = "success", Failure = "failure", - OperationCompleted = "operation-completed" + OperationCompleted = "operationCompleted" } interface AuthenticationResult { diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts index 1fe2d0a124..7a95395af1 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts @@ -43,7 +43,7 @@ export enum AuthenticationResultStatus { Redirect = 'redirect', Success = 'success', Failure = 'failure', - OperationCompleted = 'operation-completed' + OperationCompleted = 'operationCompleted' }; export interface AuthenticationResult { diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs index d272cebbd3..4af828b37f 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// /// Gets or sets the status of the authentication operation. The status can be one of . /// - public string Status { get; set; } + public RemoteAuthenticationStatus Status { get; set; } /// /// Gets or sets the error message of a failed authentication operation. diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs index a433fa1357..08cf1a69a0 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs @@ -6,27 +6,27 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// /// Represents the status of an authentication operation. /// - public class RemoteAuthenticationStatus + public enum RemoteAuthenticationStatus { /// /// The application is going to be redirected. /// - public const string Redirect = "redirect"; + Redirect, /// /// The authentication operation completed successfully. /// - public const string Success = "success"; + Success, /// /// There was an error performing the authentication operation. /// - public const string Failure = "failure"; + Failure, /// /// The operation in the current navigation context has completed. This signals that the application running on the /// current browser context is about to be shut down and no other work is required. /// - public const string OperationCompleted = "operation-completed"; + OperationCompleted, } } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs index 95aacd90da..9c1c74933c 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs @@ -40,5 +40,17 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// [JsonPropertyName("post_logout_redirect_uri")] public string PostLogoutRedirectUri { get; set; } + + /// + /// Gets or sets the response type to use on the authorization flow. The valid values are specified by the identity provider metadata. + /// + [JsonPropertyName("response_type")] + public string ResponseType { get; set; } + + /// + /// Gets or sets the response mode to use in the authorization flow. + /// + [JsonPropertyName("response_mode")] + public string ResponseMode { get; set; } } } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs index c62c24e1e3..d60d0282d2 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs @@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication await NavigateToReturnUrl(uri); break; default: - throw new InvalidOperationException($"Invalid authentication result status '{result.Status ?? "(null)"}'."); + throw new InvalidOperationException($"Invalid authentication result status."); } } else @@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication await NavigateToReturnUrl(uri); break; default: - throw new InvalidOperationException($"Invalid authentication result status '{result.Status ?? "(null)"}'."); + throw new InvalidOperationException($"Invalid authentication result status."); } } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs index 1653472d0b..387adb3aaa 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// The status of the result. /// The in case it was successful. /// The redirect uri to go to for provisioning the token. - public AccessTokenResult(string status, AccessToken token, string redirectUrl) + public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, string redirectUrl) { Status = status; _token = token; @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// /// Gets or sets the status of the current operation. See for a list of statuses. /// - public string Status { get; set; } + public AccessTokenResultStatus Status { get; set; } /// /// Gets or sets the URL to redirect to if is . @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// true when the token request is successful; false otherwise. public bool TryGetToken(out AccessToken accessToken) { - if (string.Equals(Status, AccessTokenResultStatus.Success, StringComparison.OrdinalIgnoreCase)) + if (Status == AccessTokenResultStatus.Success) { accessToken = _token; return true; diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs index c26bd419e0..15d311413f 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs @@ -6,16 +6,16 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication /// /// Represents the possible results from trying to acquire an access token. /// - public class AccessTokenResultStatus + public enum AccessTokenResultStatus { /// /// The token was successfully acquired. /// - public const string Success = "success"; + Success, /// /// A redirect is needed in order to provision the token. /// - public const string RequiresRedirect = "requiesRedirect"; + RequiresRedirect, } } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs index e92419fcbc..cf1bfdc5c7 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs @@ -70,7 +70,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication RemoteAuthenticationContext context) { await EnsureAuthService(); - var result = await _jsRuntime.InvokeAsync>("AuthenticationService.signIn", context.State); + var internalResult = await _jsRuntime.InvokeAsync>("AuthenticationService.signIn", context.State); + var result = internalResult.Convert(); if (result.Status == RemoteAuthenticationStatus.Success) { UpdateUser(GetUser()); @@ -84,7 +85,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication RemoteAuthenticationContext context) { await EnsureAuthService(); - var result = await _jsRuntime.InvokeAsync>("AuthenticationService.completeSignIn", context.Url); + var internalResult = await _jsRuntime.InvokeAsync>("AuthenticationService.completeSignIn", context.Url); + var result = internalResult.Convert(); if (result.Status == RemoteAuthenticationStatus.Success) { UpdateUser(GetUser()); @@ -98,7 +100,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication RemoteAuthenticationContext context) { await EnsureAuthService(); - var result = await _jsRuntime.InvokeAsync>("AuthenticationService.signOut", context.State); + var internalResult = await _jsRuntime.InvokeAsync>("AuthenticationService.signOut", context.State); + var result = internalResult.Convert(); if (result.Status == RemoteAuthenticationStatus.Success) { UpdateUser(GetUser()); @@ -112,7 +115,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication RemoteAuthenticationContext context) { await EnsureAuthService(); - var result = await _jsRuntime.InvokeAsync>("AuthenticationService.completeSignOut", context.Url); + var internalResult = await _jsRuntime.InvokeAsync>("AuthenticationService.completeSignOut", context.Url); + var result = internalResult.Convert(); if (result.Status == RemoteAuthenticationStatus.Success) { UpdateUser(GetUser()); @@ -127,13 +131,18 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication await EnsureAuthService(); var result = await _jsRuntime.InvokeAsync("AuthenticationService.getAccessToken"); - if (string.Equals(result.Status, AccessTokenResultStatus.RequiresRedirect, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(result.Status, ignoreCase: true, out var parsedStatus)) + { + throw new InvalidOperationException($"Invalid access token result status '{result.Status ?? "(null)"}'"); + } + + if (parsedStatus == AccessTokenResultStatus.RequiresRedirect) { var redirectUrl = GetRedirectUrl(null); result.RedirectUrl = redirectUrl.ToString(); } - return new AccessTokenResult(result.Status, result.Token, result.RedirectUrl); + return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl); } /// @@ -147,13 +156,18 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication await EnsureAuthService(); var result = await _jsRuntime.InvokeAsync("AuthenticationService.getAccessToken", options); - if (string.Equals(result.Status, AccessTokenResultStatus.RequiresRedirect, StringComparison.OrdinalIgnoreCase)) + if (!Enum.TryParse(result.Status, ignoreCase: true, out var parsedStatus)) + { + throw new InvalidOperationException($"Invalid access token result status '{result.Status ?? "(null)"}'"); + } + + if (parsedStatus == AccessTokenResultStatus.RequiresRedirect) { var redirectUrl = GetRedirectUrl(options.ReturnUrl); result.RedirectUrl = redirectUrl.ToString(); } - return new AccessTokenResult(result.Status, result.Token, result.RedirectUrl); + return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl); } private Uri GetRedirectUrl(string customReturnUrl) @@ -242,4 +256,32 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication public AccessToken Token { get; set; } public string RedirectUrl { get; set; } } + + // Internal for testing purposes + internal struct InternalRemoteAuthenticationResult where TRemoteAuthenticationState : RemoteAuthenticationState + { + public string Status { get; set; } + + public string ErrorMessage { get; set; } + + public TRemoteAuthenticationState State { get; set; } + + public RemoteAuthenticationResult Convert() + { + var result = new RemoteAuthenticationResult(); + result.ErrorMessage = ErrorMessage; + result.State = State; + + if (Status != null && Enum.TryParse(Status, ignoreCase: true, out var status)) + { + result.Status = status; + } + else + { + throw new InvalidOperationException($"Can't convert status '${Status ?? "(null)"}'."); + } + + return result; + } + } } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs index f46fe50e0d..7e5bcf7b15 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs @@ -27,10 +27,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.SignInResult = new RemoteAuthenticationResult + testJsRuntime.SignInResult = new InternalRemoteAuthenticationResult { State = state, - Status = RemoteAuthenticationStatus.Success + Status = RemoteAuthenticationStatus.Success.ToString() }; // Act @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [InlineData(RemoteAuthenticationStatus.Redirect)] [InlineData(RemoteAuthenticationStatus.Failure)] [InlineData(RemoteAuthenticationStatus.OperationCompleted)] - public async Task RemoteAuthenticationService_SignIn_DoesNotUpdateUserOnOtherResult(string value) + public async Task RemoteAuthenticationService_SignIn_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) { // Arrange var testJsRuntime = new TestJsRuntime(); @@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.SignInResult = new RemoteAuthenticationResult + testJsRuntime.SignInResult = new InternalRemoteAuthenticationResult { - Status = value + Status = value.ToString() }; // Act @@ -83,10 +83,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.CompleteSignInResult = new RemoteAuthenticationResult + testJsRuntime.CompleteSignInResult = new InternalRemoteAuthenticationResult { State = state, - Status = RemoteAuthenticationStatus.Success + Status = RemoteAuthenticationStatus.Success.ToString() }; // Act @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [InlineData(RemoteAuthenticationStatus.Redirect)] [InlineData(RemoteAuthenticationStatus.Failure)] [InlineData(RemoteAuthenticationStatus.OperationCompleted)] - public async Task RemoteAuthenticationService_CompleteSignInAsync_DoesNotUpdateUserOnOtherResult(string value) + public async Task RemoteAuthenticationService_CompleteSignInAsync_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) { // Arrange var testJsRuntime = new TestJsRuntime(); @@ -113,9 +113,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.CompleteSignInResult = new RemoteAuthenticationResult + testJsRuntime.CompleteSignInResult = new InternalRemoteAuthenticationResult { - Status = value + Status = value.ToString().ToString() }; // Act @@ -139,10 +139,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.SignOutResult = new RemoteAuthenticationResult + testJsRuntime.SignOutResult = new InternalRemoteAuthenticationResult { State = state, - Status = RemoteAuthenticationStatus.Success + Status = RemoteAuthenticationStatus.Success.ToString() }; // Act @@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [InlineData(RemoteAuthenticationStatus.Redirect)] [InlineData(RemoteAuthenticationStatus.Failure)] [InlineData(RemoteAuthenticationStatus.OperationCompleted)] - public async Task RemoteAuthenticationService_SignOut_DoesNotUpdateUserOnOtherResult(string value) + public async Task RemoteAuthenticationService_SignOut_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) { // Arrange var testJsRuntime = new TestJsRuntime(); @@ -169,9 +169,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.SignOutResult = new RemoteAuthenticationResult + testJsRuntime.SignOutResult = new InternalRemoteAuthenticationResult { - Status = value + Status = value.ToString() }; // Act @@ -195,10 +195,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.CompleteSignOutResult = new RemoteAuthenticationResult + testJsRuntime.CompleteSignOutResult = new InternalRemoteAuthenticationResult { State = state, - Status = RemoteAuthenticationStatus.Success + Status = RemoteAuthenticationStatus.Success.ToString() }; // Act @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [InlineData(RemoteAuthenticationStatus.Redirect)] [InlineData(RemoteAuthenticationStatus.Failure)] [InlineData(RemoteAuthenticationStatus.OperationCompleted)] - public async Task RemoteAuthenticationService_CompleteSignOutAsync_DoesNotUpdateUserOnOtherResult(string value) + public async Task RemoteAuthenticationService_CompleteSignOutAsync_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) { // Arrange var testJsRuntime = new TestJsRuntime(); @@ -225,9 +225,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication new TestNavigationManager()); var state = new RemoteAuthenticationState(); - testJsRuntime.CompleteSignOutResult = new RemoteAuthenticationResult + testJsRuntime.CompleteSignOutResult = new InternalRemoteAuthenticationResult { - Status = value + Status = value.ToString() }; // Act @@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication var state = new RemoteAuthenticationState(); testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult { - Status = AccessTokenResultStatus.Success, + Status = "success", Token = new AccessToken { Value = "1234", @@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); Assert.True(result.TryGetToken(out var token)); - Assert.Equal(result.Status, testJsRuntime.GetAccessTokenResult.Status); + Assert.Equal(result.Status, Enum.Parse(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); Assert.Equal(result.RedirectUrl, testJsRuntime.GetAccessTokenResult.RedirectUrl); Assert.Equal(token, testJsRuntime.GetAccessTokenResult.Token); } @@ -290,7 +290,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication var state = new RemoteAuthenticationState(); testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult { - Status = AccessTokenResultStatus.RequiresRedirect, + Status = "requiresRedirect", }; var tokenOptions = new AccessTokenRequestOptions @@ -310,7 +310,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication Assert.False(result.TryGetToken(out var token)); Assert.Null(token); - Assert.Equal(result.Status, testJsRuntime.GetAccessTokenResult.Status); + Assert.Equal(result.Status, Enum.Parse(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); Assert.Equal(expectedRedirectUrl, result.RedirectUrl); Assert.Equal(tokenOptions, (AccessTokenRequestOptions)testJsRuntime.PastInvocations[^1].args[0]); } @@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication var state = new RemoteAuthenticationState(); testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult { - Status = AccessTokenResultStatus.RequiresRedirect, + Status = "requiresRedirect", }; var tokenOptions = new AccessTokenRequestOptions @@ -350,7 +350,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication Assert.False(result.TryGetToken(out var token)); Assert.Null(token); - Assert.Equal(result.Status, testJsRuntime.GetAccessTokenResult.Status); + Assert.Equal(result.Status, Enum.Parse(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); Assert.Equal(expectedRedirectUrl, result.RedirectUrl); Assert.Equal(tokenOptions, (AccessTokenRequestOptions)testJsRuntime.PastInvocations[^1].args[0]); } @@ -435,7 +435,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication testJsRuntime.GetUserResult = JsonSerializer.Deserialize>(serializedUser); testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult { - Status = AccessTokenResultStatus.Success, + Status = "success", Token = new AccessToken { Value = "1234", @@ -499,15 +499,13 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication { public IList<(string identifier, object[] args)> PastInvocations { get; set; } = new List<(string, object[])>(); - public RemoteAuthenticationResult SignInResult { get; set; } + public InternalRemoteAuthenticationResult SignInResult { get; set; } - public RemoteAuthenticationResult CompleteSignInResult { get; set; } + public InternalRemoteAuthenticationResult CompleteSignInResult { get; set; } - public RemoteAuthenticationResult SignOutResult { get; set; } + public InternalRemoteAuthenticationResult SignOutResult { get; set; } - public RemoteAuthenticationResult CompleteSignOutResult { get; set; } - - public RemoteAuthenticationResult InitResult { get; set; } + public InternalRemoteAuthenticationResult CompleteSignOutResult { get; set; } public InternalAccessTokenResult GetAccessTokenResult { get; set; }