Skip to content

Commit ea09c7f

Browse files
committed
Update the console sandbox to use the Google integration
1 parent 7dcd7a0 commit ea09c7f

File tree

6 files changed

+148
-51
lines changed

6 files changed

+148
-51
lines changed

sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
3636

3737
try
3838
{
39+
var registration = await _service.GetClientRegistrationByProviderNameAsync(provider, stoppingToken);
3940
var configuration = await _service.GetServerConfigurationByProviderNameAsync(provider, stoppingToken);
4041

41-
if (await AuthenticateUserInteractivelyAsync(configuration, stoppingToken))
42+
if (await AuthenticateUserInteractivelyAsync(registration, configuration, stoppingToken))
4243
{
43-
var flow = await GetSelectedFlowAsync(configuration, stoppingToken);
44+
var flow = await GetSelectedFlowAsync(registration, configuration, stoppingToken);
4445

4546
AnsiConsole.MarkupLine("[cyan]Launching the system browser.[/]");
4647

@@ -144,7 +145,7 @@ await _service.AuthenticateInteractivelyAsync(new()
144145

145146
else
146147
{
147-
var type = await GetSelectedGrantTypeAsync(configuration, stoppingToken);
148+
var type = await GetSelectedGrantTypeAsync(registration, configuration, stoppingToken);
148149
if (type is GrantTypes.DeviceCode)
149150
{
150151
// Ask OpenIddict to send a device authorization request and write
@@ -439,34 +440,48 @@ async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenI
439440
}
440441

441442
Task<(string? GrantType, string? ResponseType)> GetSelectedFlowAsync(
443+
OpenIddictClientRegistration registration,
442444
OpenIddictConfiguration configuration, CancellationToken cancellationToken)
443445
{
444-
static (string? GrantType, string? ResponseType) Prompt(OpenIddictConfiguration configuration)
446+
static (string? GrantType, string? ResponseType) Prompt(
447+
OpenIddictClientRegistration registration, OpenIddictConfiguration configuration)
445448
{
446449
List<((string? GrantType, string? ResponseType), string DisplayName)> choices = [];
447450

448-
var types = configuration.ResponseTypesSupported.Select(type =>
451+
var types = configuration.ResponseTypesSupported.Select(static type =>
449452
new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)));
450453

451454
if (configuration.GrantTypesSupported.Contains(GrantTypes.AuthorizationCode) &&
452-
types.Any(type => type.Count is 1 && type.Contains(ResponseTypes.Code)))
455+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.AuthorizationCode)) &&
456+
types.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.Code)) &&
457+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
458+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
459+
.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.Code))))
453460
{
454461
choices.Add(((
455462
GrantType : GrantTypes.AuthorizationCode,
456463
ResponseType: ResponseTypes.Code), "Authorization code flow"));
457464
}
458465

459-
if (configuration.GrantTypesSupported.Contains(GrantTypes.Implicit))
466+
if (configuration.GrantTypesSupported.Contains(GrantTypes.Implicit) &&
467+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.Implicit)))
460468
{
461-
if (types.Any(type => type.Count is 1 && type.Contains(ResponseTypes.IdToken)))
469+
if (types.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.IdToken)) &&
470+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
471+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
472+
.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.IdToken))))
462473
{
463474
choices.Add(((
464475
GrantType : GrantTypes.Implicit,
465476
ResponseType: ResponseTypes.IdToken), "Implicit flow (id_token)"));
466477
}
467478

468-
if (types.Any(type => type.Count is 2 && type.Contains(ResponseTypes.IdToken) &&
469-
type.Contains(ResponseTypes.Token)))
479+
if (types.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.IdToken) &&
480+
type.Contains(ResponseTypes.Token)) &&
481+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
482+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
483+
.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.IdToken) &&
484+
type.Contains(ResponseTypes.Token))))
470485
{
471486
choices.Add(((
472487
GrantType : GrantTypes.Implicit,
@@ -475,36 +490,54 @@ async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenI
475490
}
476491

477492
if (configuration.GrantTypesSupported.Contains(GrantTypes.AuthorizationCode) &&
478-
configuration.GrantTypesSupported.Contains(GrantTypes.Implicit))
493+
configuration.GrantTypesSupported.Contains(GrantTypes.Implicit) &&
494+
(registration.GrantTypes.Count is 0 || (registration.GrantTypes.Contains(GrantTypes.AuthorizationCode) &&
495+
registration.GrantTypes.Contains(GrantTypes.Implicit))))
479496
{
480-
if (types.Any(type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
481-
type.Contains(ResponseTypes.IdToken)))
497+
if (types.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
498+
type.Contains(ResponseTypes.IdToken)) &&
499+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
500+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
501+
.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
502+
type.Contains(ResponseTypes.IdToken))))
482503
{
483504
choices.Add(((
484505
GrantType : GrantTypes.AuthorizationCode,
485506
ResponseType: ResponseTypes.Code + ' ' + ResponseTypes.IdToken), "Hybrid flow (code + id_token)"));
486507
}
487508

488-
if (types.Any(type => type.Count is 3 && type.Contains(ResponseTypes.Code) &&
509+
if (types.Any(static type => type.Count is 3 && type.Contains(ResponseTypes.Code) &&
489510
type.Contains(ResponseTypes.IdToken) &&
490-
type.Contains(ResponseTypes.Token)))
511+
type.Contains(ResponseTypes.Token)) &&
512+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
513+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
514+
.Any(static type => type.Count is 3 && type.Contains(ResponseTypes.Code) &&
515+
type.Contains(ResponseTypes.IdToken) &&
516+
type.Contains(ResponseTypes.Token))))
491517
{
492518
choices.Add(((
493519
GrantType : GrantTypes.AuthorizationCode,
494520
ResponseType: ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token),
495521
"Hybrid flow (code + id_token + token)"));
496522
}
497523

498-
if (types.Any(type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
499-
type.Contains(ResponseTypes.Token)))
524+
if (types.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
525+
type.Contains(ResponseTypes.Token)) &&
526+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
527+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
528+
.Any(static type => type.Count is 2 && type.Contains(ResponseTypes.Code) &&
529+
type.Contains(ResponseTypes.Token))))
500530
{
501531
choices.Add(((
502532
GrantType : GrantTypes.AuthorizationCode,
503533
ResponseType: ResponseTypes.Code + ' ' + ResponseTypes.Token), "Hybrid flow (code + token)"));
504534
}
505535
}
506536

507-
if (types.Any(type => type.Count is 1 && type.Contains(ResponseTypes.None)))
537+
if (types.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.None)) &&
538+
(registration.ResponseTypes.Count is 0 || registration.ResponseTypes
539+
.Select(static type => new HashSet<string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))
540+
.Any(static type => type.Count is 1 && type.Contains(ResponseTypes.None))))
508541
{
509542
choices.Add(((
510543
GrantType : null,
@@ -524,36 +557,42 @@ async Task<string> PromptAsync() => AnsiConsole.Prompt(new SelectionPrompt<OpenI
524557
.UseConverter(choice => choice.DisplayName)).Item1;
525558
}
526559

527-
return WaitAsync(Task.Run(() => Prompt(configuration), cancellationToken), cancellationToken);
560+
return WaitAsync(Task.Run(() => Prompt(registration, configuration), cancellationToken), cancellationToken);
528561
}
529562

530-
Task<string> GetSelectedGrantTypeAsync(OpenIddictConfiguration configuration, CancellationToken cancellationToken)
563+
Task<string> GetSelectedGrantTypeAsync(
564+
OpenIddictClientRegistration registration,
565+
OpenIddictConfiguration configuration, CancellationToken cancellationToken)
531566
{
532-
static string Prompt(OpenIddictConfiguration configuration)
567+
static string Prompt(OpenIddictClientRegistration registration, OpenIddictConfiguration configuration)
533568
{
534569
List<(string GrantType, string DisplayName)> choices = [];
535570

536571
if (configuration.GrantTypesSupported.Contains(GrantTypes.DeviceCode) &&
537572
configuration.DeviceAuthorizationEndpoint is not null &&
538-
configuration.TokenEndpoint is not null)
573+
configuration.TokenEndpoint is not null &&
574+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.DeviceCode)))
539575
{
540576
choices.Add((GrantTypes.DeviceCode, "Device authorization code grant"));
541577
}
542578

543579
if (configuration.GrantTypesSupported.Contains(GrantTypes.Password) &&
544-
configuration.TokenEndpoint is not null)
580+
configuration.TokenEndpoint is not null &&
581+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.Password)))
545582
{
546583
choices.Add((GrantTypes.Password, "Resource owner password credentials grant"));
547584
}
548585

549586
if (configuration.GrantTypesSupported.Contains(GrantTypes.TokenExchange) &&
550-
configuration.TokenEndpoint is not null)
587+
configuration.TokenEndpoint is not null &&
588+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.TokenExchange)))
551589
{
552590
choices.Add((GrantTypes.TokenExchange, "Token exchange"));
553591
}
554592

555593
if (configuration.GrantTypesSupported.Contains(GrantTypes.ClientCredentials) &&
556-
configuration.TokenEndpoint is not null)
594+
configuration.TokenEndpoint is not null &&
595+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.ClientCredentials)))
557596
{
558597
choices.Add((GrantTypes.ClientCredentials, "Client credentials grant (application authentication only)"));
559598
}
@@ -569,10 +608,11 @@ configuration.DeviceAuthorizationEndpoint is not null &&
569608
.UseConverter(choice => choice.DisplayName)).GrantType;
570609
}
571610

572-
return WaitAsync(Task.Run(() => Prompt(configuration), cancellationToken), cancellationToken);
611+
return WaitAsync(Task.Run(() => Prompt(registration, configuration), cancellationToken), cancellationToken);
573612
}
574613

575614
Task<bool> AuthenticateUserInteractivelyAsync(
615+
OpenIddictClientRegistration registration,
576616
OpenIddictConfiguration configuration, CancellationToken cancellationToken)
577617
{
578618
static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
@@ -583,10 +623,34 @@ static bool Prompt() => AnsiConsole.Prompt(new ConfirmationPrompt(
583623
ShowDefaultValue = true
584624
});
585625

586-
if (configuration.GrantTypesSupported.Contains(GrantTypes.AuthorizationCode) ||
587-
configuration.GrantTypesSupported.Contains(GrantTypes.Implicit))
626+
if (configuration.GrantTypesSupported.Contains(GrantTypes.AuthorizationCode) &&
627+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.AuthorizationCode)))
628+
{
629+
if (configuration.GrantTypesSupported.Any(static type => type is not (
630+
GrantTypes.AuthorizationCode or GrantTypes.Implicit or GrantTypes.RefreshToken)) &&
631+
(registration.GrantTypes.Count is 0 ||
632+
registration.GrantTypes.Any(static type => type is not (
633+
GrantTypes.AuthorizationCode or GrantTypes.Implicit or GrantTypes.RefreshToken))))
634+
{
635+
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
636+
}
637+
638+
return Task.FromResult(true);
639+
}
640+
641+
if (configuration.GrantTypesSupported.Contains(GrantTypes.Implicit) &&
642+
(registration.GrantTypes.Count is 0 || registration.GrantTypes.Contains(GrantTypes.Implicit)))
588643
{
589-
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
644+
if (configuration.GrantTypesSupported.Any(static type => type is not (
645+
GrantTypes.AuthorizationCode or GrantTypes.Implicit or GrantTypes.RefreshToken)) &&
646+
(registration.GrantTypes.Count is 0 ||
647+
registration.GrantTypes.Any(static type => type is not (
648+
GrantTypes.AuthorizationCode or GrantTypes.Implicit or GrantTypes.RefreshToken))))
649+
{
650+
return WaitAsync(Task.Run(Prompt, cancellationToken), cancellationToken);
651+
}
652+
653+
return Task.FromResult(true);
590654
}
591655

592656
return Task.FromResult(false);

sandbox/OpenIddict.Sandbox.Console.Client/Program.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,38 @@
8989
.AddGitHub(options =>
9090
{
9191
options.SetClientId("992372d088f8676a7945")
92+
// Note: GitHub doesn't allow creating public clients and requires using a secret. While this
93+
// is a discouraged practice, it is the only option to use this provider in a desktop client.
9294
.SetClientSecret("1f18c22f766e44d7bd4ea4a6510b9e337d48ab38")
9395
.SetRedirectUri("callback/login/github");
9496
})
97+
// Note: Google requires using separate client registrations to be able to use the authorization code flow
98+
// and device flow in the same application. To work around this limitation, two registrations are used but
99+
// each one explicitly restricts the grant types that OpenIddict is allowed to negotiate dynamically.
100+
.AddGoogle(options =>
101+
{
102+
options.SetClientId("1016114395689-arf09f1g51hadci5p5hn6lpp798k8rql.apps.googleusercontent.com")
103+
// Note: Google doesn't allow creating public clients and requires using a secret. While this
104+
// is discouraged practice, it is the only option to use this provider in a desktop client.
105+
.SetClientSecret("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")
106+
.SetRedirectUri("callback/login/google")
107+
.SetAccessType("offline")
108+
.AddScopes(Scopes.Profile)
109+
.AddGrantTypes(GrantTypes.AuthorizationCode)
110+
.SetProviderName("Google [code flow]")
111+
.SetProviderDisplayName("Google (authorization code grant-only)");
112+
})
113+
.AddGoogle(options =>
114+
{
115+
options.SetClientId("1016114395689-le5kvnikv5hhg3otvn1tgs2aogpkpvff.apps.googleusercontent.com")
116+
.SetClientSecret("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")
117+
.SetRedirectUri("callback/login/google")
118+
.SetAccessType("offline")
119+
.AddScopes(Scopes.Profile)
120+
.AddGrantTypes(GrantTypes.DeviceCode)
121+
.SetProviderName("Google [device flow]")
122+
.SetProviderDisplayName("Google (device code grant-only)");
123+
})
95124
.AddTwitter(options =>
96125
{
97126
options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")

sandbox/OpenIddict.Sandbox.Maui.Client/MauiProgram.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public static MauiApp CreateMauiApp()
9292
.AddTwitter(options =>
9393
{
9494
options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")
95-
// Note: Twitter doesn't support the recommended ":/" syntax and requires using "://".
95+
// Note: Twitter doesn't support the recommended ":/" syntax and requires using "://".
9696
.SetRedirectUri("com.openiddict.sandbox.maui.client://callback/login/twitter");
9797
});
9898
});

sandbox/OpenIddict.Sandbox.WinForms.Client/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
.AddGitHub(options =>
9090
{
9191
options.SetClientId("cf8efb4d76c0cb7109d3")
92+
// Note: GitHub doesn't allow creating public clients and requires using a secret. While this
93+
// is discouraged practice, it is the only option to use this provider in a desktop client.
9294
.SetClientSecret("e8c0f6b869164411bb9052e42414cbcc52d518cd")
9395
// Note: GitHub doesn't support the recommended ":/" syntax and requires using "://".
9496
.SetRedirectUri("com.openiddict.sandbox.winforms.client://callback/login/github");

0 commit comments

Comments
 (0)