Skip to content

Commit 3b0fed3

Browse files
authored
Add Webflow provider (#1047)
Add Webflow provider.
1 parent 26d4c6d commit 3b0fed3

11 files changed

+335
-0
lines changed

AspNet.Security.OAuth.Providers.sln

+8
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A
221221
docs\workweixin.md = docs\workweixin.md
222222
docs\xumm.md = docs\xumm.md
223223
docs\zendesk.md = docs\zendesk.md
224+
docs\webflow.md = docs\webflow.md
224225
docs\miro.md = docs\miro.md
225226
docs\linear.md = docs\linear.md
226227
EndProjectSection
@@ -320,6 +321,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.GitCo
320321
EndProject
321322
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Atlassian", "src\AspNet.Security.OAuth.Atlassian\AspNet.Security.OAuth.Atlassian.csproj", "{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}"
322323
EndProject
324+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Webflow", "src\AspNet.Security.OAuth.Webflow\AspNet.Security.OAuth.Webflow.csproj", "{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}"
325+
EndProject
323326
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Miro", "src\AspNet.Security.OAuth.Miro\AspNet.Security.OAuth.Miro.csproj", "{7F22DE22-FDE8-4A14-AA65-D5B36098533E}"
324327
EndProject
325328
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Linear", "src\AspNet.Security.OAuth.Linear\AspNet.Security.OAuth.Linear.csproj", "{B1167108-CA36-4C6B-85B0-1C7F5A24E4A4}"
@@ -746,6 +749,10 @@ Global
746749
{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.Build.0 = Debug|Any CPU
747750
{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.ActiveCfg = Release|Any CPU
748751
{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.Build.0 = Release|Any CPU
752+
{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
753+
{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Debug|Any CPU.Build.0 = Debug|Any CPU
754+
{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.ActiveCfg = Release|Any CPU
755+
{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664}.Release|Any CPU.Build.0 = Release|Any CPU
749756
{7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
750757
{7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Debug|Any CPU.Build.0 = Debug|Any CPU
751758
{7F22DE22-FDE8-4A14-AA65-D5B36098533E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -869,6 +876,7 @@ Global
869876
{F3E62C24-5F82-4CF5-A994-0E10D04FB495} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
870877
{668833D5-DB6A-475F-B0FD-A03462B037B8} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
871878
{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
879+
{F5DA8A08-5089-4076-B0FC-3F4A5CBB9664} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
872880
{7F22DE22-FDE8-4A14-AA65-D5B36098533E} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
873881
{B1167108-CA36-4C6B-85B0-1C7F5A24E4A4} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
874882
EndGlobalSection

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ If a provider you're looking for does not exist, consider making a PR to add one
245245
| Visual Studio (Azure DevOps) | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.VisualStudio?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.VisualStudio/ "Download AspNet.Security.OAuth.VisualStudio from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.VisualStudio?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.VisualStudio "Download AspNet.Security.OAuth.VisualStudio from MyGet.org") | [Documentation](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops "Azure DevOps developer documentation") |
246246
| VK ID | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.VkId?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.VkId/ "Download AspNet.Security.OAuth.VkId from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.VkId?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.VkId "Download AspNet.Security.OAuth.VkId from MyGet.org") | [Documentation](https://id.vk.com/about/business/go/docs/en/vkid/latest/vk-id/connection/start-integration/auth-without-sdk-web/ "VK ID developer documentation") |
247247
| Vkontakte | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Vkontakte?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Vkontakte/ "Download AspNet.Security.OAuth.Vkontakte from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Vkontakte?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Vkontakte "Download AspNet.Security.OAuth.Vkontakte from MyGet.org") | [Documentation](https://vk.com/dev/access_token "Vkontakte developer documentation") |
248+
| Webflow | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Webflow?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Webflow/ "Download AspNet.Security.OAuth.Webflow from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Webflow?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Webflow "Download AspNet.Security.OAuth.Webflow from MyGet.org") | [Documentation](https://developers.webflow.com/v2.0.0/data/reference/oauth-app "Webflow developer documentation") |
248249
| Weibo | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Weibo?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Weibo/ "Download AspNet.Security.OAuth.Weibo from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Weibo?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Weibo "Download AspNet.Security.OAuth.Weibo from MyGet.org") | [Documentation](https://open.weibo.com/wiki/Oauth/en "Weibo developer documentation") |
249250
| Weixin (WeChat) | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.Weixin?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.Weixin/ "Download AspNet.Security.OAuth.Weixin from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.Weixin?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Weixin "Download AspNet.Security.OAuth.Weixin from MyGet.org") | [Documentation](https://open.wechat.com/cgi-bin/newreadtemplate?t=overseas_open/docs/web/login/login "WeChat developer documentation") |
250251
| WordPress | [![NuGet](https://img.shields.io/nuget/v/AspNet.Security.OAuth.WordPress?logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/AspNet.Security.OAuth.WordPress/ "Download AspNet.Security.OAuth.WordPress from NuGet.org") | [![MyGet](https://img.shields.io/myget/aspnet-contrib/vpre/AspNet.Security.OAuth.WordPress?logo=nuget&label=MyGet&color=blue)](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.WordPress "Download AspNet.Security.OAuth.WordPress from MyGet.org") | [Documentation](https://developer.wordpress.com/docs/oauth2/ "WordPress developer documentation") |

docs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ covered by the section above.
7878
| Twitch | _Optional_ | [Documentation](twitch.md "Twitch provider documentation") |
7979
| Twitter | _Optional_ | [Documentation](twitter.md "Twitter provider documentation") |
8080
| Vkontakte | _Optional_ | [Documentation](vkontakte.md "Vkontakte provider documentation") |
81+
| Webflow | _Optional_ | [Documentation](webflow.md "Webflow provider documentation") |
8182
| Weibo | _Optional_ | [Documentation](weibo.md "Weibo provider documentation") |
8283
| WorkWeixin (WeCom) | _Optional_ | [Documentation](workweixin.md "WorkWeixin provider documentation") |
8384
| Xero | _Optional_ | [Documentation](xero.md "Xero provider documentation") |

docs/webflow.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Integrating the Webflow Provider
2+
3+
> [!IMPORTANT]
4+
> When you create the OAuth application in Webflow, make sure to enable the Authorized user (Read-only) [scope](https://developers.webflow.com/v2.0.0/data/reference/scopes). If this scope is not enabled, retrieving the user information will fail, resulting in the entire authorization flow to fail.
5+
6+
## Example
7+
8+
```csharp
9+
services.AddAuthentication(options => /* Auth configuration */)
10+
.AddWebflow(options =>
11+
{
12+
options.ClientId = configuration["Webflow:ClientId"] ?? string.Empty;
13+
options.ClientSecret = configuration["Webflow:ClientSecret"] ?? string.Empty;
14+
})
15+
```
16+
17+
## Required Additional Settings
18+
19+
_None._
20+
21+
## Optional Settings
22+
23+
_None._
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<PackageValidationBaselineVersion>9.2.0</PackageValidationBaselineVersion>
5+
<!-- TODO Remove once published to NuGet.org -->
6+
<DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
7+
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
8+
</PropertyGroup>
9+
10+
<PropertyGroup>
11+
<Description>ASP.NET Core security middleware enabling Webflow authentication.</Description>
12+
<Authors>Jerrie Pelser</Authors>
13+
<PackageTags>aspnetcore;authentication;webflow;oauth;security</PackageTags>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
18+
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
19+
</ItemGroup>
20+
21+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
namespace AspNet.Security.OAuth.Webflow;
8+
9+
/// <summary>
10+
/// Default values used by the Webflow authentication middleware.
11+
/// </summary>
12+
public static class WebflowAuthenticationDefaults
13+
{
14+
/// <summary>
15+
/// Default value for <see cref="AuthenticationScheme.Name"/>.
16+
/// </summary>
17+
public const string AuthenticationScheme = "Webflow";
18+
19+
/// <summary>
20+
/// Default value for <see cref="AuthenticationScheme.DisplayName"/>.
21+
/// </summary>
22+
public static readonly string DisplayName = "Webflow";
23+
24+
/// <summary>
25+
/// Default value for <see cref="AuthenticationSchemeOptions.ClaimsIssuer"/>.
26+
/// </summary>
27+
public static readonly string Issuer = "Webflow";
28+
29+
/// <summary>
30+
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
31+
/// </summary>
32+
public static readonly string CallbackPath = "/signin-webflow";
33+
34+
/// <summary>
35+
/// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
36+
/// </summary>
37+
public static readonly string AuthorizationEndpoint = "https://webflow.com/oauth/authorize";
38+
39+
/// <summary>
40+
/// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
41+
/// </summary>
42+
public static readonly string TokenEndpoint = "https://api.webflow.com/oauth/access_token";
43+
44+
/// <summary>
45+
/// Default value for <see cref="OAuthOptions.UserInformationEndpoint"/>.
46+
/// </summary>
47+
public static readonly string UserInformationEndpoint = "https://api.webflow.com/v2/token/authorized_by";
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using AspNet.Security.OAuth.Webflow;
8+
using Microsoft.AspNetCore.Authentication;
9+
10+
namespace Microsoft.Extensions.DependencyInjection;
11+
12+
/// <summary>
13+
/// Extension methods to add Webflow authentication capabilities to an HTTP application pipeline.
14+
/// </summary>
15+
public static class WebflowAuthenticationExtensions
16+
{
17+
/// <summary>
18+
/// Adds <see cref="WebflowAuthenticationHandler"/> to the specified
19+
/// <see cref="AuthenticationBuilder"/>, which enables Webflow authentication capabilities.
20+
/// </summary>
21+
/// <param name="builder">The authentication builder.</param>
22+
/// <returns>A reference to this instance after the operation has completed.</returns>
23+
public static AuthenticationBuilder AddWebflow([NotNull] this AuthenticationBuilder builder)
24+
{
25+
return builder.AddWebflow(WebflowAuthenticationDefaults.AuthenticationScheme, options => { });
26+
}
27+
28+
/// <summary>
29+
/// Adds <see cref="WebflowAuthenticationHandler"/> to the specified
30+
/// <see cref="AuthenticationBuilder"/>, which enables Webflow authentication capabilities.
31+
/// </summary>
32+
/// <param name="builder">The authentication builder.</param>
33+
/// <param name="configuration">The delegate used to configure the Webflow authentication options.</param>
34+
/// <returns>A reference to this instance after the operation has completed.</returns>
35+
public static AuthenticationBuilder AddWebflow(
36+
[NotNull] this AuthenticationBuilder builder,
37+
[NotNull] Action<WebflowAuthenticationOptions> configuration)
38+
{
39+
return builder.AddWebflow(WebflowAuthenticationDefaults.AuthenticationScheme, configuration);
40+
}
41+
42+
/// <summary>
43+
/// Adds <see cref="WebflowAuthenticationHandler"/> to the specified
44+
/// <see cref="AuthenticationBuilder"/>, which enables Webflow authentication capabilities.
45+
/// </summary>
46+
/// <param name="builder">The authentication builder.</param>
47+
/// <param name="scheme">The authentication scheme associated with this instance.</param>
48+
/// <param name="configuration">The delegate used to configure the Webflow authentication options.</param>
49+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
50+
public static AuthenticationBuilder AddWebflow(
51+
[NotNull] this AuthenticationBuilder builder,
52+
[NotNull] string scheme,
53+
[NotNull] Action<WebflowAuthenticationOptions> configuration)
54+
{
55+
return builder.AddWebflow(scheme, WebflowAuthenticationDefaults.DisplayName, configuration);
56+
}
57+
58+
/// <summary>
59+
/// Adds <see cref="WebflowAuthenticationHandler"/> to the specified
60+
/// <see cref="AuthenticationBuilder"/>, which enables Webflow authentication capabilities.
61+
/// </summary>
62+
/// <param name="builder">The authentication builder.</param>
63+
/// <param name="scheme">The authentication scheme associated with this instance.</param>
64+
/// <param name="caption">The optional display name associated with this instance.</param>
65+
/// <param name="configuration">The delegate used to configure the Webflow authentication options.</param>
66+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
67+
public static AuthenticationBuilder AddWebflow(
68+
[NotNull] this AuthenticationBuilder builder,
69+
[NotNull] string scheme,
70+
[NotNull] string caption,
71+
[NotNull] Action<WebflowAuthenticationOptions> configuration)
72+
{
73+
return builder.AddOAuth<WebflowAuthenticationOptions, WebflowAuthenticationHandler>(scheme, caption, configuration);
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System.Net.Http.Headers;
8+
using System.Security.Claims;
9+
using System.Text.Encodings.Web;
10+
using System.Text.Json;
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.Options;
13+
14+
namespace AspNet.Security.OAuth.Webflow;
15+
16+
public partial class WebflowAuthenticationHandler(
17+
[NotNull] IOptionsMonitor<WebflowAuthenticationOptions> options,
18+
[NotNull] ILoggerFactory logger,
19+
[NotNull] UrlEncoder encoder) : OAuthHandler<WebflowAuthenticationOptions>(options, logger, encoder)
20+
{
21+
protected override async Task<AuthenticationTicket> CreateTicketAsync(
22+
[NotNull] ClaimsIdentity identity,
23+
[NotNull] AuthenticationProperties properties,
24+
[NotNull] OAuthTokenResponse tokens)
25+
{
26+
using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
27+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
28+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
29+
30+
using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
31+
if (!response.IsSuccessStatusCode)
32+
{
33+
await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted);
34+
throw new HttpRequestException("An error occurred while retrieving the user profile from Webflow.");
35+
}
36+
37+
using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
38+
39+
var principal = new ClaimsPrincipal(identity);
40+
var context = new OAuthCreatingTicketContext(
41+
principal,
42+
properties,
43+
Context,
44+
Scheme,
45+
Options,
46+
Backchannel,
47+
tokens,
48+
payload.RootElement);
49+
context.RunClaimActions();
50+
51+
await Events.CreatingTicket(context);
52+
return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
53+
}
54+
55+
private static partial class Log
56+
{
57+
internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken)
58+
{
59+
UserProfileError(
60+
logger,
61+
response.StatusCode,
62+
response.Headers.ToString(),
63+
await response.Content.ReadAsStringAsync(cancellationToken));
64+
}
65+
66+
[LoggerMessage(1,
67+
LogLevel.Error,
68+
"An error occurred while retrieving the user profile: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")]
69+
private static partial void UserProfileError(
70+
ILogger logger,
71+
System.Net.HttpStatusCode status,
72+
string headers,
73+
string body);
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System.Security.Claims;
8+
9+
namespace AspNet.Security.OAuth.Webflow;
10+
11+
/// <summary>
12+
/// Defines a set of options used by <see cref="WebflowAuthenticationHandler"/>.
13+
/// </summary>
14+
public class WebflowAuthenticationOptions : OAuthOptions
15+
{
16+
public WebflowAuthenticationOptions()
17+
{
18+
ClaimsIssuer = WebflowAuthenticationDefaults.Issuer;
19+
20+
CallbackPath = WebflowAuthenticationDefaults.CallbackPath;
21+
22+
AuthorizationEndpoint = WebflowAuthenticationDefaults.AuthorizationEndpoint;
23+
TokenEndpoint = WebflowAuthenticationDefaults.TokenEndpoint;
24+
UserInformationEndpoint = WebflowAuthenticationDefaults.UserInformationEndpoint;
25+
26+
Scope.Add("authorized_user:read");
27+
28+
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
29+
ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
30+
ClaimActions.MapJsonKey(ClaimTypes.GivenName, "firstName");
31+
ClaimActions.MapJsonKey(ClaimTypes.Surname, "lastName");
32+
}
33+
}

0 commit comments

Comments
 (0)