Skip to content

Commit c8ae943

Browse files
Chapelinkevinchalet
authored andcommitted
Add a Patreon provider
1 parent 9a68a2b commit c8ae943

9 files changed

+362
-2
lines changed

AspNet.Security.OAuth.Providers.sln

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26228.10
4+
VisualStudioVersion = 15.0.26430.16
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}"
77
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{CA7BFFF8-0DC9-4621-8DFE-D3E6A116C509}"
9+
EndProject
810
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{BAC7067D-88FE-4385-8AC9-1A325FFBDE69}"
911
EndProject
1012
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.GitHub", "src\AspNet.Security.OAuth.GitHub\AspNet.Security.OAuth.GitHub.csproj", "{9DFEE19E-C170-4F20-93F3-EC952B1532F2}"
@@ -99,7 +101,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Amazo
99101
EndProject
100102
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Discord", "src\AspNet.Security.OAuth.Discord\AspNet.Security.OAuth.Discord.csproj", "{86614CB9-0768-40BF-8C27-699E3990B733}"
101103
EndProject
102-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{CA7BFFF8-0DC9-4621-8DFE-D3E6A116C509}"
104+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Patreon", "src\AspNet.Security.OAuth.Patreon\AspNet.Security.OAuth.Patreon.csproj", "{ED8A220C-45FE-45C6-9B8F-BB009ACE972E}"
103105
EndProject
104106
Global
105107
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -291,6 +293,10 @@ Global
291293
{86614CB9-0768-40BF-8C27-699E3990B733}.Debug|Any CPU.Build.0 = Debug|Any CPU
292294
{86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.ActiveCfg = Release|Any CPU
293295
{86614CB9-0768-40BF-8C27-699E3990B733}.Release|Any CPU.Build.0 = Release|Any CPU
296+
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
297+
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Debug|Any CPU.Build.0 = Debug|Any CPU
298+
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.ActiveCfg = Release|Any CPU
299+
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E}.Release|Any CPU.Build.0 = Release|Any CPU
294300
EndGlobalSection
295301
GlobalSection(SolutionProperties) = preSolution
296302
HideSolutionNode = FALSE
@@ -342,5 +348,6 @@ Global
342348
{9AC900E8-6D03-412B-8D9B-302FD0FBD059} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
343349
{C66A6EDB-D29A-49EE-841E-75F239DE5A04} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
344350
{86614CB9-0768-40BF-8C27-699E3990B733} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
351+
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
345352
EndGlobalSection
346353
EndGlobal

samples/Mvc.Client/Mvc.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.MailChimp\AspNet.Security.OAuth.MailChimp.csproj" />
3333
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Myob\AspNet.Security.OAuth.Myob.csproj" />
3434
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Onshape\AspNet.Security.OAuth.Onshape.csproj" />
35+
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Patreon\AspNet.Security.OAuth.Patreon.csproj" />
3536
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Paypal\AspNet.Security.OAuth.Paypal.csproj" />
3637
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Reddit\AspNet.Security.OAuth.Reddit.csproj" />
3738
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.Salesforce\AspNet.Security.OAuth.Salesforce.csproj" />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Import Project="..\..\build\packages.props" />
4+
5+
<PropertyGroup>
6+
<TargetFrameworks>net451;netstandard1.3</TargetFrameworks>
7+
</PropertyGroup>
8+
9+
<PropertyGroup>
10+
<Description>ASP.NET Core security middleware enabling Patreon authentication.</Description>
11+
<Authors>Antoine Bichon</Authors>
12+
<PackageTags>aspnetcore;authentication;oauth;patreon;security</PackageTags>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<Compile Include="..\..\shared\AspNet.Security.OAuth.Extensions\*.cs" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
21+
<PackageReference Include="Microsoft.AspNetCore.Authentication.OAuth" Version="$(AspNetCoreVersion)" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 48 additions & 0 deletions
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+
using Microsoft.AspNetCore.Builder;
8+
9+
namespace AspNet.Security.OAuth.Patreon
10+
{
11+
public class PatreonAuthenticationDefaults
12+
{
13+
/// <summary>
14+
/// Default value for <see cref="AuthenticationOptions.AuthenticationScheme"/>.
15+
/// </summary>
16+
public const string AuthenticationScheme = "Patreon";
17+
18+
/// <summary>
19+
/// Default value for <see cref="RemoteAuthenticationOptions.DisplayName"/>.
20+
/// </summary>
21+
public const string DisplayName = "Patreon";
22+
23+
/// <summary>
24+
/// Default value for <see cref="AuthenticationOptions.ClaimsIssuer"/>.
25+
/// </summary>
26+
public const string Issuer = "Patreon";
27+
28+
/// <summary>
29+
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
30+
/// </summary>
31+
public const string CallbackPath = "/signin-patreon";
32+
33+
/// <summary>
34+
/// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
35+
/// </summary>
36+
public const string AuthorizationEndpoint = "https://www.patreon.com/oauth2/authorize";
37+
38+
/// <summary>
39+
/// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
40+
/// </summary>
41+
public const string TokenEndpoint = "https://api.patreon.com/oauth2/token";
42+
43+
/// <summary>
44+
/// Default value for <see cref="OAuthOptions.UserInformationEndpoint"/>.
45+
/// </summary>
46+
public const string UserInformationEndpoint = "https://api.patreon.com/oauth2/api/current_user";
47+
}
48+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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;
8+
using AspNet.Security.OAuth.Patreon;
9+
using JetBrains.Annotations;
10+
using Microsoft.Extensions.Options;
11+
12+
namespace Microsoft.AspNetCore.Builder
13+
{
14+
/// <summary>
15+
/// Extension methods to add Patreon authentication capabilities to an HTTP application pipeline.
16+
/// </summary>
17+
public static class PatreonAuthenticationExtensions
18+
{
19+
/// <summary>
20+
/// Adds the <see cref="PatreonAuthenticationMiddleware"/> middleware to the specified
21+
/// <see cref="IApplicationBuilder"/>, which enables Patreon authentication capabilities.
22+
/// </summary>
23+
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
24+
/// <param name="options">A <see cref="PatreonAuthenticationOptions"/> that specifies options for the middleware.</param>
25+
/// <returns>A reference to this instance after the operation has completed.</returns>
26+
/// <exception cref="ArgumentNullException">
27+
/// <paramref name="app"/> or <paramref name="options"/> is <see langword="null"/>.
28+
/// </exception>
29+
public static IApplicationBuilder UsePatreonAuthentication(
30+
[NotNull] this IApplicationBuilder app,
31+
[NotNull] PatreonAuthenticationOptions options)
32+
{
33+
if (app == null)
34+
{
35+
throw new ArgumentNullException(nameof(app));
36+
}
37+
38+
if (options == null)
39+
{
40+
throw new ArgumentNullException(nameof(options));
41+
}
42+
43+
return app.UseMiddleware<PatreonAuthenticationMiddleware>(Options.Create(options));
44+
}
45+
46+
/// <summary>
47+
/// Adds the <see cref="PatreonAuthenticationMiddleware"/> middleware to the specified
48+
/// <see cref="IApplicationBuilder"/>, which enables Patreon authentication capabilities.
49+
/// </summary>
50+
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
51+
/// <param name="configuration">An action delegate to configure the provided <see cref="PatreonAuthenticationOptions"/>.</param>
52+
/// <returns>A reference to this instance after the operation has completed.</returns>
53+
/// <exception cref="ArgumentNullException">
54+
/// <paramref name="app"/> or <paramref name="configuration"/> is <see langword="null"/>.
55+
/// </exception>
56+
public static IApplicationBuilder UsePatreonAuthentication(
57+
[NotNull] this IApplicationBuilder app,
58+
[NotNull] Action<PatreonAuthenticationOptions> configuration)
59+
{
60+
if (app == null)
61+
{
62+
throw new ArgumentNullException(nameof(app));
63+
}
64+
65+
if (configuration == null)
66+
{
67+
throw new ArgumentNullException(nameof(configuration));
68+
}
69+
70+
var options = new PatreonAuthenticationOptions();
71+
configuration(options);
72+
73+
return app.UseMiddleware<PatreonAuthenticationMiddleware>(Options.Create(options));
74+
}
75+
}
76+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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;
8+
using System.Net.Http.Headers;
9+
using System.Security.Claims;
10+
using System.Threading.Tasks;
11+
using AspNet.Security.OAuth.Extensions;
12+
using JetBrains.Annotations;
13+
using Microsoft.AspNetCore.Authentication;
14+
using Microsoft.AspNetCore.Authentication.OAuth;
15+
using Microsoft.AspNetCore.Http.Authentication;
16+
using Microsoft.Extensions.Logging;
17+
using Newtonsoft.Json.Linq;
18+
19+
namespace AspNet.Security.OAuth.Patreon
20+
{
21+
public class PatreonAuthenticationHandler : OAuthHandler<PatreonAuthenticationOptions>
22+
{
23+
public PatreonAuthenticationHandler([NotNull] HttpClient client)
24+
: base(client)
25+
{
26+
}
27+
28+
protected override async Task<AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
29+
[NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
30+
{
31+
var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
32+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
33+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
34+
35+
var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
36+
if (!response.IsSuccessStatusCode)
37+
{
38+
Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
39+
"returned a {Status} response with the following payload: {Headers} {Body}.",
40+
/* Status: */ response.StatusCode,
41+
/* Headers: */ response.Headers.ToString(),
42+
/* Body: */ await response.Content.ReadAsStringAsync());
43+
44+
throw new HttpRequestException("An error occurred while retrieving the user profile.");
45+
}
46+
47+
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
48+
49+
identity.AddOptionalClaim(ClaimTypes.NameIdentifier, PatreonAuthenticationHelper.GetUserIdentifier(payload), Options.ClaimsIssuer)
50+
.AddOptionalClaim(ClaimTypes.Name, PatreonAuthenticationHelper.GetUserName(payload), Options.ClaimsIssuer)
51+
.AddOptionalClaim(ClaimTypes.Webpage, PatreonAuthenticationHelper.GetLink(payload), Options.ClaimsIssuer)
52+
.AddOptionalClaim("urn:patreon:avatar", PatreonAuthenticationHelper.GetAvatarLink(payload), Options.ClaimsIssuer);
53+
54+
var principal = new ClaimsPrincipal(identity);
55+
var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
56+
57+
var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens);
58+
await Options.Events.CreatingTicket(context);
59+
60+
return context.Ticket;
61+
}
62+
}
63+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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;
8+
using JetBrains.Annotations;
9+
using Newtonsoft.Json.Linq;
10+
11+
namespace AspNet.Security.OAuth.Patreon
12+
{
13+
/// <summary>
14+
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
15+
/// instance retrieved from Patreon after a successful authentication process.
16+
/// </summary>
17+
public static class PatreonAuthenticationHelper
18+
{
19+
/// <summary>
20+
/// Gets the identifier corresponding to the authenticated user.
21+
/// </summary>
22+
public static string GetUserIdentifier([NotNull] JObject user)
23+
{
24+
if (user == null)
25+
{
26+
throw new ArgumentNullException(nameof(user));
27+
}
28+
29+
return user["data"]?.Value<string>("id");
30+
}
31+
32+
/// <summary>
33+
/// Gets the name corresponding to the authenticated user.
34+
/// </summary>
35+
public static string GetUserName([NotNull] JObject user)
36+
{
37+
if (user == null)
38+
{
39+
throw new ArgumentNullException(nameof(user));
40+
}
41+
42+
return user["data"]?["attributes"]?.Value<string>("full_name");
43+
}
44+
45+
/// <summary>
46+
/// Gets the page URL corresponding to the authenticated user.
47+
/// </summary>
48+
public static string GetLink([NotNull] JObject user)
49+
{
50+
if (user == null)
51+
{
52+
throw new ArgumentNullException(nameof(user));
53+
}
54+
55+
return user["data"]?["attributes"]?.Value<string>("url");
56+
}
57+
58+
/// <summary>
59+
/// Gets the avatar URL corresponding to the authenticated user.
60+
/// </summary>
61+
public static string GetAvatarLink([NotNull] JObject user)
62+
{
63+
if (user == null)
64+
{
65+
throw new ArgumentNullException(nameof(user));
66+
}
67+
68+
return user["data"]?["attributes"]?.Value<string>("thumb_url");
69+
}
70+
}
71+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.Text.Encodings.Web;
8+
using JetBrains.Annotations;
9+
using Microsoft.AspNetCore.Authentication;
10+
using Microsoft.AspNetCore.Authentication.OAuth;
11+
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.AspNetCore.Http;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
16+
namespace AspNet.Security.OAuth.Patreon
17+
{
18+
public class PatreonAuthenticationMiddleware : OAuthMiddleware<PatreonAuthenticationOptions>
19+
{
20+
public PatreonAuthenticationMiddleware(
21+
[NotNull] RequestDelegate next,
22+
[NotNull] IDataProtectionProvider dataProtectionProvider,
23+
[NotNull] ILoggerFactory loggerFactory,
24+
[NotNull] UrlEncoder encoder,
25+
[NotNull] IOptions<SharedAuthenticationOptions> sharedOptions,
26+
[NotNull] IOptions<PatreonAuthenticationOptions> options)
27+
: base(next, dataProtectionProvider, loggerFactory, encoder, sharedOptions, options)
28+
{
29+
}
30+
31+
protected override AuthenticationHandler<PatreonAuthenticationOptions> CreateHandler()
32+
{
33+
return new PatreonAuthenticationHandler(Backchannel);
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)