implemented Discord OAuth2

This commit is contained in:
t.ruspekhofer 2022-02-13 20:40:15 +01:00
parent 9365e22874
commit dbf1be4c5d
18 changed files with 195 additions and 133 deletions

View file

@ -0,0 +1,12 @@
namespace Discord.OAuth2
{
public static class DiscordDefaults
{
public const string AuthenticationScheme = "Discord";
public const string DisplayName = "Discord";
public static readonly string AuthorizationEndpoint = "https://discordapp.com/api/oauth2/authorize";
public static readonly string TokenEndpoint = "https://discordapp.com/api/oauth2/token";
public static readonly string UserInformationEndpoint = "https://discordapp.com/api/users/@me";
}
}

View file

@ -0,0 +1,22 @@

using System;
using Microsoft.AspNetCore.Authentication;
using Discord.OAuth2;
namespace Microsoft.Extensions.DependencyInjection
{
public static class DiscordAuthenticationOptionsExtensions
{
public static AuthenticationBuilder AddDiscord(this AuthenticationBuilder builder)
=> builder.AddDiscord(DiscordDefaults.AuthenticationScheme, _ => { });
public static AuthenticationBuilder AddDiscord(this AuthenticationBuilder builder, Action<DiscordOptions> configureOptions)
=> builder.AddDiscord(DiscordDefaults.AuthenticationScheme, configureOptions);
public static AuthenticationBuilder AddDiscord(this AuthenticationBuilder builder, string authenticationScheme, Action<DiscordOptions> configureOptions)
=> builder.AddDiscord(authenticationScheme, DiscordDefaults.DisplayName, configureOptions);
public static AuthenticationBuilder AddDiscord(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<DiscordOptions> configureOptions)
=> builder.AddOAuth<DiscordOptions, DiscordHandler>(authenticationScheme, displayName, configureOptions);
}
}

View file

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
namespace Discord.OAuth2
{
internal class DiscordHandler : OAuthHandler<DiscordOptions>
{
public DiscordHandler(IOptionsMonitor<DiscordOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
{
var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await Backchannel.SendAsync(request, Context.RequestAborted);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"Failed to retrieve Discord user information ({response.StatusCode}).");
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
context.RunClaimActions();
await Events.CreatingTicket(context);
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
}
}
}

View file

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace Discord.OAuth2
{
/// <summary> Configuration options for <see cref="DiscordHandler"/>. </summary>
public class DiscordOptions : OAuthOptions
{
/// <summary> Initializes a new <see cref="DiscordOptions"/>. </summary>
public DiscordOptions()
{
CallbackPath = new PathString("/signin-discord");
AuthorizationEndpoint = DiscordDefaults.AuthorizationEndpoint;
TokenEndpoint = DiscordDefaults.TokenEndpoint;
UserInformationEndpoint = DiscordDefaults.UserInformationEndpoint;
Scope.Add("identify");
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id", ClaimValueTypes.UInteger64);
ClaimActions.MapJsonKey(ClaimTypes.Name, "username", ClaimValueTypes.String);
ClaimActions.MapJsonKey(ClaimTypes.Email, "email", ClaimValueTypes.Email);
ClaimActions.MapJsonKey("urn:discord:discriminator", "discriminator", ClaimValueTypes.UInteger32);
ClaimActions.MapJsonKey("urn:discord:avatar", "avatar", ClaimValueTypes.String);
ClaimActions.MapJsonKey("urn:discord:verified", "verified", ClaimValueTypes.Boolean);
}
/// <summary> Gets or sets the Discord-assigned appId. </summary>
public string AppId { get => ClientId; set => ClientId = value; }
/// <summary> Gets or sets the Discord-assigned app secret. </summary>
public string AppSecret { get => ClientSecret; set => ClientSecret = value; }
}
}