public async Task<AuthorizeResult> AuthorizeAsync(bool trySilent = false, object extraParameters = null)
        {
            InvokeResult wviResult;
            AuthorizeResult result = new AuthorizeResult
            {
                IsError = true,
            };

            // todo: replace with CryptoRandom
            result.Nonce = Guid.NewGuid().ToString("N");
            result.RedirectUri = _options.RedirectUri;
            string codeChallenge = CreateCodeChallenge(result);
            var url = await CreateUrlAsync(result, codeChallenge, extraParameters);
            var webViewOptions = new InvokeOptions(url, _options.RedirectUri);
            if (trySilent)
            {
                webViewOptions.InitialDisplayMode = DisplayMode.Hidden;
            }
            if (_options.UseFormPost)
            {
                webViewOptions.ResponseMode = ResponseMode.FormPost;
            }

            // try silent mode if requested
            wviResult = await _options.WebView.InvokeAsync(webViewOptions);

            if (wviResult.ResultType == InvokeResultType.Success)
            {
                return await ParseResponse(wviResult.Response, result);
            }

            result.Error = wviResult.ResultType.ToString();
            return result;
        }
        private async Task<LoginResult> ValidateAsync(AuthorizeResult result)
        {
            // validate identity token
            var principal = await ValidateIdentityTokenAsync(result.IdentityToken);
            if (principal == null)
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "identity token validation error"
                };
            }

            // validate nonce
            var tokenNonce = principal.FindFirst("nonce")?.Value ?? "";
            if (!string.Equals(result.Nonce, tokenNonce))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid nonce"
                };
            }

            // validate audience
            var audience = principal.FindFirst("aud")?.Value ?? "";
            if (!string.Equals(_options.ClientId, audience))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid audience"
                };
            }

            // validate c_hash
            var cHash = principal.FindFirst("c_hash")?.Value ?? "";

            var sha256 = HashAlgorithmProvider.OpenAlgorithm("SHA256");

            var codeHash = sha256.HashData(
                CryptographicBuffer.CreateFromByteArray(
                    Encoding.ASCII.GetBytes(result.Code)));

            byte[] codeHashArray;
            CryptographicBuffer.CopyToByteArray(codeHash, out codeHashArray);

            byte[] leftPart = new byte[16];
            Array.Copy(codeHashArray, leftPart, 16);

            var leftPartB64 = Base64Url.Encode(leftPart);

            if (!leftPartB64.Equals(cHash))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid code"
                };
            }

            var endpoints = await _options.GetEndpointsAsync();

            // get access token
            var tokenClient = new TokenClient(endpoints.Token, _options.ClientId, _options.ClientSecret);
            var tokenResult = await tokenClient.RequestAuthorizationCodeAsync(
                result.Code, 
                result.RedirectUri, 
                codeVerifier: result.Verifier);

            if (tokenResult.IsError || tokenResult.IsHttpError)
            {
                return new LoginResult
                {
                    Success = false,
                    Error = tokenResult.Error
                };
            }

            // get profile if enabled
            if (_options.LoadProfile)
            {
                var userInfoClient = new UserInfoClient(new Uri(endpoints.UserInfo), tokenResult.AccessToken);
                var userInfoResponse = await userInfoClient.GetAsync();

                var primaryClaimTypes = principal.Claims.Select(c => c.Type).Distinct();

                foreach (var claim in userInfoResponse.Claims.Where(c => !primaryClaimTypes.Contains(c.Item1)))
                {
                    principal.Identities.First().AddClaim(new Claim(claim.Item1, claim.Item2));
                }

            }

            // success
            return new LoginResult
            {
                Success = true,
                Principal = FilterProtocolClaims(principal),
                AccessToken = tokenResult.AccessToken,
                RefreshToken = tokenResult.RefreshToken,
                AccessTokenExpiration = DateTime.Now.AddSeconds(tokenResult.ExpiresIn),
                IdentityToken = result.IdentityToken,
                AuthenticationTime = DateTime.Now
            };
        }
        private string CreateCodeChallenge(AuthorizeResult result)
        {
            if (_options.UseProofKeys)
            {
                // todo: replace with CryptoRandom
                result.Verifier = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N");
                var sha256 = HashAlgorithmProvider.OpenAlgorithm("SHA256");

                var challengeBuffer = sha256.HashData(
                    CryptographicBuffer.CreateFromByteArray(
                        Encoding.ASCII.GetBytes(result.Verifier)));
                byte[] challengeBytes;

                CryptographicBuffer.CopyToByteArray(challengeBuffer, out challengeBytes);
                return Base64Url.Encode(challengeBytes);
            }
            else
            {
                return null;
            }
        }
        private Task<AuthorizeResult> ParseResponse(string webViewResponse, AuthorizeResult result)
        {
            var response = new AuthorizeResponse(webViewResponse);

            if (response.IsError)
            {
                result.Error = response.Error;
                return Task.FromResult(result);
            }

            if (string.IsNullOrEmpty(response.Code))
            {
                result.Error = "Missing authorization code";
                return Task.FromResult(result);
            }

            if (string.IsNullOrEmpty(response.IdentityToken))
            {
                result.Error = "Missing identity token";
                return Task.FromResult(result);
            }

            result.IdentityToken = response.IdentityToken;
            result.Code = response.Code;
            result.IsError = false;

            return Task.FromResult(result);
        }
        private async Task<string> CreateUrlAsync(AuthorizeResult result, string codeChallenge, object extraParameters)
        {
            var request = new AuthorizeRequest((await _options.GetEndpointsAsync()).Authorize);
            var url = request.CreateAuthorizeUrl(
                clientId: _options.ClientId,
                responseType: OidcConstants.ResponseTypes.CodeIdToken,
                scope: _options.Scope,
                redirectUri: result.RedirectUri,
                responseMode: _options.UseFormPost ? OidcConstants.ResponseModes.FormPost : null,
                nonce: result.Nonce,
                codeChallenge: codeChallenge,
                codeChallengeMethod: _options.UseProofKeys ? OidcConstants.CodeChallengeMethods.Sha256 : null,
                extra: extraParameters);

            return url;
        }