protected override async Task AuthorizeWithBrowserAsync()

in sources/Google.Solutions.Apis/Auth/Gaia/GaiaOidcClient.cs [177:283]


        protected override async Task<IOidcSession> AuthorizeWithBrowserAsync(
            OidcOfflineCredential? offlineCredential,
            ICodeReceiver codeReceiver,
            CancellationToken cancellationToken)
        {
            Precondition.Expect(offlineCredential == null ||
                offlineCredential.Issuer == OidcIssuer.Gaia,
                "Offline credential must be issued by Gaia");

            codeReceiver.ExpectNotNull(nameof(codeReceiver));

            var initializer = new GaiaCodeFlow.Initializer(
                this.endpoint,
                this.deviceEnrollment,
                this.userAgent)
            {
                ClientSecrets = this.Registration.ToClientSecrets()
            };

            if (offlineCredential?.IdToken != null &&
                UnverifiedGaiaJsonWebToken.TryDecode(offlineCredential.IdToken, out var offlineIdToken) &&
                offlineIdToken != null &&
                !string.IsNullOrEmpty(offlineIdToken.Payload.Email))
            {
                //
                // We still have an ID token with an email address, so we can perform
                // a "minimal flow":
                //
                //  - use existing email as login hint (to skip account chooser)
                //  - don't request the email scope again so that consent unbundling
                //    doesn't apply
                //
                // NB. The last point is important and the entire point why we're storing
                // the ID token: Consent unbundling (i.e., the behavior of the OAuth consent
                // screen where it shows unchecked checkboxes for all scopes) only applies
                // when we request two or more scopes. By only requesting a single scope,
                // we can sidestep consent unbundling, thereby improving UX.
                //

                initializer.LoginHint = offlineIdToken.Payload.Email;
                initializer.Scopes = new[] { Scopes.Cloud };
            }
            else
            {
                initializer.Scopes = new[] { Scopes.Cloud, Scopes.Email };
            }

            try
            {
                var flow = CreateFlow(initializer);
                var app = new AuthorizationCodeInstalledApp(flow, codeReceiver);

                var apiCredential = await
                    app.AuthorizeAsync(null, cancellationToken)
                    .ConfigureAwait(true);

                //
                // Verify that all requested scopes have been granted.
                //
                var grantedScopes = apiCredential.Token.Scope?.Split(' ');
                if (initializer.Scopes.Any(
                    requestedScope => !grantedScopes.Contains(requestedScope)))
                {
                    throw new OAuthScopeNotGrantedException(
                        "Authorization failed because you have denied access to a " +
                        "required resource. Sign in again and make sure " +
                        "to grant access to all requested resources.");
                }

                try
                {
                    //
                    // N.B. Do not dispose the flow if the sign-in succeeds as the
                    // credential object must hold on to it.
                    //
                    return CreateSessionAndRegisterTerminateEvent(
                        flow,
                        offlineCredential,
                        apiCredential.Token);
                }
                catch
                {
                    flow.Dispose();
                    throw;
                }
            }
            catch (TokenResponseException e) when (
                e.Error?.ErrorUri != null &&
                e.Error.ErrorUri.StartsWith("https://accounts.google.com/info/servicerestricted"))
            {
                if (this.deviceEnrollment.State == DeviceEnrollmentState.Enrolled)
                {
                    throw new AuthorizationFailedException(
                        "Authorization failed because your computer's device certificate is " +
                        "is invalid or unrecognized. Use the Endpoint Verification extension " +
                        "to verify that your computer is enrolled and try again.\n\n" +
                        e.Error.ErrorDescription);
                }
                else
                {
                    throw new AuthorizationFailedException(
                        "Authorization failed because a context-aware access requirement " +
                        "was not met.\n\n" + e.Error.ErrorDescription);

                }
            }
        }