Skip to content

Commit 02e5017

Browse files
authored
Discord feature 367 prompt field (#415)
* Added the ability to set a "prompt" query string parameter on the Authorization Endpoint URL. * Added Discord tests for the Authorization Endpoint Uri prompt query string. * Converted Discord prompt enum to string, modified Discord unit tests. Added comments to unit tests. * Making requested changes. * Updating Promp comments.
1 parent 5c62ccb commit 02e5017

File tree

4 files changed

+129
-1
lines changed

4 files changed

+129
-1
lines changed

src/AspNet.Security.OAuth.Discord/DiscordAuthenticationConstants.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,11 @@ public static class Claims
2323
public const string AvatarUrl = "urn:discord:avatar:url";
2424
public const string Discriminator = "urn:discord:user:discriminator";
2525
}
26+
27+
public static class UrlQueryParameterValues
28+
{
29+
public const string Consent = "consent";
30+
public const string None = "none";
31+
}
2632
}
2733
}

src/AspNet.Security.OAuth.Discord/DiscordAuthenticationHandler.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* for more information concerning the license and the contributors participating to this project.
55
*/
66

7+
using System.Globalization;
78
using System.Net.Http;
89
using System.Net.Http.Headers;
910
using System.Security.Claims;
@@ -13,6 +14,7 @@
1314
using JetBrains.Annotations;
1415
using Microsoft.AspNetCore.Authentication;
1516
using Microsoft.AspNetCore.Authentication.OAuth;
17+
using Microsoft.AspNetCore.WebUtilities;
1618
using Microsoft.Extensions.Logging;
1719
using Microsoft.Extensions.Options;
1820

@@ -29,6 +31,21 @@ public DiscordAuthenticationHandler(
2931
{
3032
}
3133

34+
/// <inheritdoc />
35+
protected override string BuildChallengeUrl(
36+
[NotNull] AuthenticationProperties properties,
37+
[NotNull] string redirectUri)
38+
{
39+
string challengeUrl = base.BuildChallengeUrl(properties, redirectUri);
40+
41+
if (!string.IsNullOrEmpty(Options.Prompt))
42+
{
43+
challengeUrl = QueryHelpers.AddQueryString(challengeUrl, "prompt", Options.Prompt);
44+
}
45+
46+
return challengeUrl;
47+
}
48+
3249
protected override async Task<AuthenticationTicket> CreateTicketAsync(
3350
[NotNull] ClaimsIdentity identity,
3451
[NotNull] AuthenticationProperties properties,

src/AspNet.Security.OAuth.Discord/DiscordAuthenticationOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Security.Claims;
99
using Microsoft.AspNetCore.Authentication;
1010
using Microsoft.AspNetCore.Authentication.OAuth;
11-
using Microsoft.AspNetCore.Http;
1211
using static AspNet.Security.OAuth.Discord.DiscordAuthenticationConstants;
1312

1413
namespace AspNet.Security.OAuth.Discord
@@ -28,6 +27,14 @@ public class DiscordAuthenticationOptions : OAuthOptions
2827
/// </summary>
2928
public string DiscordAvatarFormat { get; set; } = Urls.AvatarUrlFormat;
3029

30+
/// <summary>
31+
/// Controls how the authorization flow handles existing authorizations.
32+
/// The default value of this property is null and the "prompt" query string parameter will not be appended to the Authorization Endpoint URL.
33+
/// Assigning this property any value other than null or an empty string will automatically append the "prompt" query string parameter to the Authorization Endpoint URL,
34+
/// with the corresponding value.
35+
/// </summary>
36+
public string? Prompt { get; set; }
37+
3138
public DiscordAuthenticationOptions()
3239
{
3340
ClaimsIssuer = DiscordAuthenticationDefaults.Issuer;

test/AspNet.Security.OAuth.Providers.Tests/Discord/DiscordTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
* for more information concerning the license and the contributors participating to this project.
55
*/
66

7+
using System;
78
using System.Security.Claims;
89
using System.Threading.Tasks;
910
using Microsoft.AspNetCore.Authentication;
11+
using Microsoft.AspNetCore.Authentication.OAuth;
1012
using Microsoft.Extensions.DependencyInjection;
13+
using Shouldly;
1114
using Xunit;
1215
using Xunit.Abstractions;
1316
using static AspNet.Security.OAuth.Discord.DiscordAuthenticationConstants;
@@ -46,5 +49,100 @@ public async Task Can_Sign_In_Using_Discord(string claimType, string claimValue)
4649
// Assert
4750
AssertClaim(claims, claimType, claimValue);
4851
}
52+
53+
[Fact]
54+
public async Task Authorization_Endpoint_Uri_by_Default_Does_Not_Contain_Prompt()
55+
{
56+
bool doesNotContainPrompt = false;
57+
58+
void ConfigureServices(IServiceCollection services)
59+
{
60+
services.PostConfigureAll<DiscordAuthenticationOptions>((options) =>
61+
{
62+
options.Events = new OAuthEvents
63+
{
64+
OnRedirectToAuthorizationEndpoint = ctx =>
65+
{
66+
doesNotContainPrompt = !ctx.RedirectUri.Contains("prompt=", StringComparison.InvariantCulture);
67+
ctx.Response.Redirect(ctx.RedirectUri);
68+
return Task.CompletedTask;
69+
}
70+
};
71+
});
72+
}
73+
74+
// Arrange
75+
using var server = CreateTestServer(ConfigureServices);
76+
77+
// Act
78+
await AuthenticateUserAsync(server);
79+
80+
// Assert
81+
doesNotContainPrompt.ShouldBeTrue();
82+
}
83+
84+
[Fact]
85+
public async Task Authorization_Endpoint_Uri_Contains_Prompt_None()
86+
{
87+
bool promptIsSetToNone = false;
88+
89+
void ConfigureServices(IServiceCollection services)
90+
{
91+
services.PostConfigureAll<DiscordAuthenticationOptions>((options) =>
92+
{
93+
options.Prompt = "none";
94+
options.Events = new OAuthEvents
95+
{
96+
OnRedirectToAuthorizationEndpoint = ctx =>
97+
{
98+
promptIsSetToNone = ctx.RedirectUri.Contains("prompt=none", StringComparison.InvariantCulture);
99+
ctx.Response.Redirect(ctx.RedirectUri);
100+
return Task.CompletedTask;
101+
}
102+
};
103+
});
104+
}
105+
106+
// Arrange
107+
using var server = CreateTestServer(ConfigureServices);
108+
109+
// Act
110+
await AuthenticateUserAsync(server);
111+
112+
// Assert
113+
promptIsSetToNone.ShouldBeTrue();
114+
}
115+
116+
[Fact]
117+
public async Task Authorization_Endpoint_Uri_Contains_Prompt_Consent()
118+
{
119+
bool promptIsSetToConsent = false;
120+
121+
void ConfigureServices(IServiceCollection services)
122+
{
123+
services.PostConfigureAll<DiscordAuthenticationOptions>((options) =>
124+
{
125+
options.Prompt = "consent";
126+
options.Events = new OAuthEvents
127+
{
128+
OnRedirectToAuthorizationEndpoint = ctx =>
129+
{
130+
promptIsSetToConsent = ctx.RedirectUri.Contains("prompt=consent", StringComparison.InvariantCulture);
131+
ctx.Response.Redirect(ctx.RedirectUri);
132+
return Task.CompletedTask;
133+
}
134+
};
135+
});
136+
}
137+
138+
// Arrange
139+
using var server = CreateTestServer(ConfigureServices);
140+
141+
// Act
142+
await AuthenticateUserAsync(server);
143+
144+
// Assert
145+
promptIsSetToConsent.ShouldBeTrue();
146+
}
49147
}
50148
}

0 commit comments

Comments
 (0)