diff --git a/OwinOAuthProviders.sln b/OwinOAuthProviders.sln
index 6141d54..1b5edd5 100644
--- a/OwinOAuthProviders.sln
+++ b/OwinOAuthProviders.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISOnline", "src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj", "{8A49FAEF-D365-4D25-942C-1CAD03845A5E}"
EndProject
@@ -94,6 +94,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Orc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OwinOAuthProvidersDemo", "OwinOAuthProvidersDemo\OwinOAuthProvidersDemo.csproj", "{5A438007-0C90-4DAC-BAA1-54A32164067F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Discord", "src\Owin.Security.Providers.Discord\Owin.Security.Providers.Discord.csproj", "{4BE728EB-778A-41AF-8DEA-0C7159711D44}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -284,6 +286,10 @@ Global
{5A438007-0C90-4DAC-BAA1-54A32164067F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A438007-0C90-4DAC-BAA1-54A32164067F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A438007-0C90-4DAC-BAA1-54A32164067F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4BE728EB-778A-41AF-8DEA-0C7159711D44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4BE728EB-778A-41AF-8DEA-0C7159711D44}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4BE728EB-778A-41AF-8DEA-0C7159711D44}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4BE728EB-778A-41AF-8DEA-0C7159711D44}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
index 4608e33..d4fd146 100755
--- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
+++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
@@ -4,6 +4,7 @@ using Microsoft.Owin.Security.Cookies;
using Owin;
//using Owin.Security.Providers.Orcid;
+//using Owin.Security.Providers.Discord;
namespace OwinOAuthProvidersDemo
{
@@ -276,6 +277,8 @@ namespace OwinOAuthProvidersDemo
//app.UseDoYouBuzzAuthentication("", "");
//app.("", "");
//app.UseOrcidAuthentication("","");
+
+ //app.UseDiscordAuthentication("", "");
}
}
}
\ No newline at end of file
diff --git a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
index 2d2c708..1b10843 100644
--- a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
+++ b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
@@ -248,6 +248,12 @@
+
+
+ {4be728eb-778a-41af-8dea-0c7159711d44}
+ Owin.Security.Providers.Discord
+
+
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
diff --git a/src/Owin.Security.Providers.Discord/Constants.cs b/src/Owin.Security.Providers.Discord/Constants.cs
new file mode 100644
index 0000000..790ca97
--- /dev/null
+++ b/src/Owin.Security.Providers.Discord/Constants.cs
@@ -0,0 +1,7 @@
+namespace Owin.Security.Providers.Discord
+{
+ internal static class Constants
+ {
+ public const string DefaultAuthenticationType = "Discord";
+ }
+}
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.Discord/DiscordAuthenticationExtensions.cs b/src/Owin.Security.Providers.Discord/DiscordAuthenticationExtensions.cs
new file mode 100644
index 0000000..ce5d36d
--- /dev/null
+++ b/src/Owin.Security.Providers.Discord/DiscordAuthenticationExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Owin.Security.Providers.Discord
+{
+ public static class DiscordAuthenticationExtensions
+ {
+ public static IAppBuilder UseDiscordAuthentication(this IAppBuilder app,
+ DiscordAuthenticationOptions options)
+ {
+ if (app == null)
+ throw new ArgumentNullException(nameof(app));
+ if (options == null)
+ throw new ArgumentNullException(nameof(options));
+
+ app.Use(typeof(DiscordAuthenticationMiddleware), app, options);
+
+ return app;
+ }
+
+ public static IAppBuilder UseDiscordAuthentication(this IAppBuilder app, string clientId, string clientSecret)
+ {
+ return app.UseDiscordAuthentication(new DiscordAuthenticationOptions
+ {
+ ClientId = clientId,
+ ClientSecret = clientSecret
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.Discord/DiscordAuthenticationHandler.cs b/src/Owin.Security.Providers.Discord/DiscordAuthenticationHandler.cs
new file mode 100644
index 0000000..926e268
--- /dev/null
+++ b/src/Owin.Security.Providers.Discord/DiscordAuthenticationHandler.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.Owin.Infrastructure;
+using Microsoft.Owin.Logging;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.Infrastructure;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Owin.Security.Providers.Discord.Provider;
+
+namespace Owin.Security.Providers.Discord
+{
+ public class DiscordAuthenticationHandler : AuthenticationHandler
+ {
+ private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
+ private const string TokenEndpoint = "https://discordapp.com/api/oauth2/token";
+ private const string UserInfoEndpoint = "https://discordapp.com/api/users/@me";
+
+ private readonly ILogger _logger;
+ private readonly HttpClient _httpClient;
+
+ public DiscordAuthenticationHandler(HttpClient httpClient, ILogger logger)
+ {
+ _httpClient = httpClient;
+ _logger = logger;
+ }
+
+ protected override async Task AuthenticateCoreAsync()
+ {
+ AuthenticationProperties properties = null;
+
+ try
+ {
+ string code = null;
+ string state = null;
+
+ var query = Request.Query;
+ var values = query.GetValues("code");
+ if (values != null && values.Count == 1)
+ {
+ code = values[0];
+ }
+ values = query.GetValues("state");
+ if (values != null && values.Count == 1)
+ {
+ state = values[0];
+ }
+
+ properties = Options.StateDataFormat.Unprotect(state);
+ if (properties == null)
+ {
+ return null;
+ }
+
+ // OAuth2 10.12 CSRF
+ if (!ValidateCorrelationId(properties, _logger))
+ {
+ return new AuthenticationTicket(null, properties);
+ }
+
+ var requestPrefix = Request.Scheme + "://" + Request.Host;
+ var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
+
+ // Build up the body for the token request
+ var body = new List>
+ {
+ new KeyValuePair("grant_type", "authorization_code"),
+ new KeyValuePair("code", code),
+ new KeyValuePair("redirect_uri", redirectUri),
+ new KeyValuePair("client_id", Options.ClientId),
+ new KeyValuePair("client_secret", Options.ClientSecret)
+ };
+ var request = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint);
+ request.Content = new FormUrlEncodedContent(body);
+
+ // Request the token
+ var tokenResponse =
+ await _httpClient.SendAsync(request);
+ tokenResponse.EnsureSuccessStatusCode();
+ var text = await tokenResponse.Content.ReadAsStringAsync();
+
+ // Deserializes the token response
+ dynamic response = JsonConvert.DeserializeObject(text);
+ var accessToken = (string)response.access_token;
+ var expires = (string)response.expires_in;
+ var refreshToken = (string)response.refresh_token;
+
+ // Get the Discord user
+ var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint);
+ userRequest.Headers.Add("Authorization", "Bearer " + Uri.EscapeDataString(accessToken) + "");
+ var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
+ graphResponse.EnsureSuccessStatusCode();
+ text = await graphResponse.Content.ReadAsStringAsync();
+ var user = JObject.Parse(text);
+
+ var context = new DiscordAuthenticatedContext(Context, user, accessToken, expires, refreshToken)
+ {
+ Identity = new ClaimsIdentity(
+ Options.AuthenticationType,
+ ClaimsIdentity.DefaultNameClaimType,
+ ClaimsIdentity.DefaultRoleClaimType)
+ };
+ if (!string.IsNullOrEmpty(context.Id))
+ {
+ context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserName, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.UserName))
+ {
+ context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Email))
+ {
+ context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Avatar))
+ {
+ context.Identity.AddClaim(new Claim("urn:discord:avatar", context.Avatar, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Discriminator))
+ {
+ context.Identity.AddClaim(new Claim("urn:discord:discriminator", context.Discriminator, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.AccessToken))
+ {
+ context.Identity.AddClaim(new Claim("urn:discord:accesstoken", context.AccessToken, XmlSchemaString, Options.AuthenticationType));
+ }
+ context.Identity.AddClaim(new Claim("urn:discord:verified", context.Verified.ToString(), XmlSchemaString, Options.AuthenticationType));
+ context.Properties = properties;
+
+ await Options.Provider.Authenticated(context);
+
+ return new AuthenticationTicket(context.Identity, context.Properties);
+ }
+ catch (Exception ex)
+ {
+ _logger.WriteError(ex.Message);
+ }
+
+ return new AuthenticationTicket(null, properties);
+ }
+
+ protected override Task ApplyResponseChallengeAsync()
+ {
+ if (Response.StatusCode != 401)
+ {
+ return Task.FromResult