diff --git a/docs/zoom.md b/docs/zoom.md deleted file mode 100644 index 60a9ecde7..000000000 --- a/docs/zoom.md +++ /dev/null @@ -1,16 +0,0 @@ -# Integrating the Zoom Provider - -## Example - -```csharp -services.AddAuthentication(options => /* Auth configuration */) - .AddZoom(options => - { - options.ClientId = "my-client-id"; - options.ClientSecret = "my-client-secret"; - }); -``` - -## Required Additional Settings - -_None._ \ No newline at end of file diff --git a/src/AspNet.Security.OAuth.Zoom/AspNet.Security.OAuth.Zoom.csproj b/src/AspNet.Security.OAuth.Zoom/AspNet.Security.OAuth.Zoom.csproj index 71ec0bb2e..badf64477 100644 --- a/src/AspNet.Security.OAuth.Zoom/AspNet.Security.OAuth.Zoom.csproj +++ b/src/AspNet.Security.OAuth.Zoom/AspNet.Security.OAuth.Zoom.csproj @@ -1,9 +1,15 @@  + 8.0.1 $(DefaultNetCoreTargetFramework) + + + true + + ASP.NET Core security middleware enabling Zoom authentication. Christian Oluwawibe diff --git a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationConstants.cs index e17500ae4..f17522cb4 100644 --- a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationConstants.cs @@ -41,8 +41,7 @@ public static class Claims public static class ProfileFields { /// - /// The unique identifier for the given memembermber. May also be referenced as the personId within a Person URN (urn:li:person:{personId}). - /// The id is unique to your specific developer application. Any attempts to use the id with other developer applications will not succeed. + /// The Unique identifier of the user /// public const string Id = "id"; @@ -72,7 +71,7 @@ public static class ProfileFields public const string PhoneNumber = "phone_number"; /// - /// Picture url of the user. + /// Picture URL of the user. /// public const string PictureUrl = "pic_url"; @@ -87,7 +86,7 @@ public static class ProfileFields public const string Verified = "verified"; /// - /// Personal meeting url of the user. + /// Personal meeting URL of the user. /// public const string PersonalMeetingUrl = "personal_meeting_url"; } diff --git a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationDefaults.cs index d7dd8771d..3cff50fd0 100644 --- a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationDefaults.cs +++ b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationDefaults.cs @@ -44,5 +44,5 @@ public class ZoomAuthenticationDefaults /// /// Default value for . /// - public static readonly string UserInformationEndpoint = "https://zoom.us/v2/users/me"; + public static readonly string UserInformationEndpoint = "https://api.zoom.us/v2/users/me"; } diff --git a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationHandler.cs index e168e6c26..52ba7817d 100644 --- a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationHandler.cs @@ -6,13 +6,10 @@ using System.Net.Http.Headers; using System.Security.Claims; -using System.Text; using System.Text.Encodings.Web; using System.Text.Json; -using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; namespace AspNet.Security.OAuth.Zoom; @@ -53,67 +50,6 @@ protected override async Task CreateTicketAsync( return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); } - protected override string BuildChallengeUrl([NotNull] AuthenticationProperties properties, [NotNull] string redirectUri) - { - // eBay uses the RuName for the redirect_uri - return base.BuildChallengeUrl(properties, redirectUri); - } - - protected override async Task ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context) - { - var tokenRequestParameters = new Dictionary() - { - ["grant_type"] = "authorization_code", - ["code"] = context.Code, - ["redirect_uri"] = context.RedirectUri, - }; - - // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl - if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier)) - { - tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier!); - context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey); - } - - using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = CreateAuthorizationHeader(); - - request.Content = new FormUrlEncodedContent(tokenRequestParameters); - - using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); - if (!response.IsSuccessStatusCode) - { - await Log.ExchangeCodeErrorAsync(Logger, response, Context.RequestAborted); - return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")); - } - - var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted)); - - return OAuthTokenResponse.Success(payload); - } - - private AuthenticationHeaderValue CreateAuthorizationHeader() - { - var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes( - string.Concat( - EscapeDataString(Options.ClientId), - ":", - EscapeDataString(Options.ClientSecret)))); - - return new AuthenticationHeaderValue("Basic", credentials); - } - - private static string EscapeDataString(string value) - { - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - return Uri.EscapeDataString(value).Replace("%20", "+", StringComparison.Ordinal); - } - private static partial class Log { internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) @@ -125,27 +61,11 @@ internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMes await response.Content.ReadAsStringAsync(cancellationToken)); } - internal static async Task ExchangeCodeErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken) - { - ExchangeCodeError( - logger, - response.StatusCode, - response.Headers.ToString(), - await response.Content.ReadAsStringAsync(cancellationToken)); - } - [LoggerMessage(1, LogLevel.Error, "An error occurred while retrieving the user profile: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")] static partial void UserProfileError( ILogger logger, System.Net.HttpStatusCode status, string headers, string body); - - [LoggerMessage(2, LogLevel.Error, "An error occurred while retrieving an access token: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")] - static partial void ExchangeCodeError( - ILogger logger, - System.Net.HttpStatusCode status, - string headers, - string body); } } diff --git a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationOptions.cs index 2f21d3ada..78e8928fd 100644 --- a/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Zoom/ZoomAuthenticationOptions.cs @@ -22,6 +22,7 @@ public ZoomAuthenticationOptions() AuthorizationEndpoint = ZoomAuthenticationDefaults.AuthorizationEndpoint; TokenEndpoint = ZoomAuthenticationDefaults.TokenEndpoint; UserInformationEndpoint = ZoomAuthenticationDefaults.UserInformationEndpoint; + UsePkce = true; ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, ProfileFields.Id); ClaimActions.MapJsonKey(ClaimTypes.Name, ProfileFields.Name); diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Zoom/ZoomTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Zoom/ZoomTests.cs index cd7df1cfa..6fcbb36f6 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Zoom/ZoomTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Zoom/ZoomTests.cs @@ -15,18 +15,7 @@ public class ZoomTests(ITestOutputHelper outputHelper) : OAuthTests - { - ConfigureDefaults(builder, options); - }); - } - - protected internal override void ConfigureApplication(IApplicationBuilder app) - { - app.UseRequestLocalization(new RequestLocalizationOptions - { - DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-US"), - }); + builder.AddZoom(options => ConfigureDefaults(builder, options)); } [Theory] @@ -35,7 +24,11 @@ protected internal override void ConfigureApplication(IApplicationBuilder app) [InlineData(ClaimTypes.Email, "frodo@shire.middleearth")] [InlineData(ClaimTypes.GivenName, "Frodo")] [InlineData(ClaimTypes.Surname, "Baggins")] + [InlineData(ZoomAuthenticationConstants.Claims.PersonalMeetingUrl, "https://us04web.zoom.us/j/5478221937?pwd=eZUKx2n11af")] + [InlineData(ZoomAuthenticationConstants.Claims.Verified, "0")] + [InlineData(ZoomAuthenticationConstants.Claims.Status, "active")] [InlineData(ZoomAuthenticationConstants.Claims.Picture, "https://upload.wikimedia.org/wikipedia/en/4/4e/Elijah_Wood_as_Frodo_Baggins.png")] + [InlineData(ClaimTypes.MobilePhone, "+2348012345678")] public async Task Can_Sign_In_Using_Zoom(string claimType, string claimValue) { await AuthenticateUserAndAssertClaimValue(claimType, claimValue); diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Zoom/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Zoom/bundle.json index bb387770f..44dcca8e5 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Zoom/bundle.json +++ b/test/AspNet.Security.OAuth.Providers.Tests/Zoom/bundle.json @@ -13,7 +13,7 @@ }, { "comment": "https://developers.zoom.us/docs/api/rest/reference/user/methods/#tag/Users", - "uri": "https://zoom.us/v2/users/me", + "uri": "https://api.zoom.us/v2/users/me", "contentFormat": "json", "contentJson": { "id": "0ECPVTrOjh", @@ -21,7 +21,11 @@ "display_name": "Frodo Baggins", "first_name": "Frodo", "last_name": "Baggins", - "pic_url": "https://upload.wikimedia.org/wikipedia/en/4/4e/Elijah_Wood_as_Frodo_Baggins.png" + "pic_url": "https://upload.wikimedia.org/wikipedia/en/4/4e/Elijah_Wood_as_Frodo_Baggins.png", + "personal_meeting_url": "https://us04web.zoom.us/j/5478221937?pwd=eZUKx2n11af", + "verified": 0, + "status": "active", + "phone_number": "+2348012345678" } } ]