コード例 #1
0
    protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
    {
        var tokenRequestParameters = new Dictionary <string, string>
        {
            ["grant_type"]   = "authorization_code",
            ["redirect_uri"] = context.RedirectUri,
            ["code"]         = context.Code,
        };

        // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
        if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier))
        {
            tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier !);
            context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey);
        }

        using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = CreateAuthorizationHeader();

        // When a custom user agent is specified in the options, add it to the request headers
        // to override the default (generic) user agent used by the OAuth2 base middleware.
        if (!string.IsNullOrEmpty(Options.UserAgent))
        {
            request.Headers.UserAgent.ParseAdd(Options.UserAgent);
        }

        request.Content = new FormUrlEncodedContent(tokenRequestParameters);

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.ExchangeCodeErrorAsync(Logger, response, Context.RequestAborted);

            return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
        }

        var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

        return(OAuthTokenResponse.Success(payload));
    }
コード例 #2
0
        protected virtual async Task <string> GetEmailAsync([NotNull] OAuthTokenResponse tokens)
        {
            // See https://developer.github.com/v3/users/emails/ for more information about the /user/emails endpoint.
            var request = new HttpRequestMessage(HttpMethod.Get, Options.UserEmailsEndpoint);

            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

            // Failed requests shouldn't cause an error: in this case, return null to indicate that the email address cannot be retrieved.
            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                return(null);
            }

            var payload = JArray.Parse(await response.Content.ReadAsStringAsync());

            return(GitHubAuthenticationHelper.GetEmail(payload));
        }
        /// <inheritdoc />
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
        {
            var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary <string, string>
            {
                { "grant_type", "authorization_code" },
                { "client_id", Options.ClientId },
                { "client_secret", MakeBankIdClientSecret(Options.ClientId, Options.ClientSecret, code) },
                { nameof(code), code },
                { "redirect_uri", redirectUri },
            });
            var response = await Backchannel.SendAsync(new HttpRequestMessage(HttpMethod.Get, address)
            {
                Headers = { Accept = { new MediaTypeWithQualityHeaderValue("application/json") } }
            },
                                                       Context.RequestAborted);

            return(response.IsSuccessStatusCode
                ? OAuthTokenResponse.Success(JObject.Parse(await response.Content.ReadAsStringAsync()))
                : OAuthTokenResponse.Failed(new Exception("OAuth token endpoint failure: " + await Display(response))));
        }
コード例 #4
0
    protected async Task <string?> GetEmailAsync([NotNull] OAuthTokenResponse tokens)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserEmailsEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.EmailAddressErrorAsync(Logger, response, Context.RequestAborted);

            throw new HttpRequestException("An error occurred while retrieving the email address associated to the user profile.");
        }

        using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

        return((from address in payload.RootElement.EnumerateArray()
                select address.GetString("email")).FirstOrDefault());
    }
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);

            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            response.EnsureSuccessStatusCode();

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            identity.AddOptionalClaim(ClaimTypes.NameIdentifier, GitHubAuthenticationHelper.GetIdentifier(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Name, GitHubAuthenticationHelper.GetLogin(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Email, GitHubAuthenticationHelper.GetEmail(payload), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:github:name", GitHubAuthenticationHelper.GetName(payload), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:github:url", GitHubAuthenticationHelper.GetLink(payload), Options.ClaimsIssuer);

            // When the email address is not public, retrieve it from the emails endpoint if the user:email scope is specified.
            if (!identity.HasClaim(claim => claim.Type == ClaimTypes.Email) && Options.Scope.Contains("user:email"))
            {
                identity.AddOptionalClaim(ClaimTypes.Email, await GetEmailAsync(tokens), Options.ClaimsIssuer);
            }

            var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens, payload)
            {
                Principal  = new ClaimsPrincipal(identity),
                Properties = properties
            };

            await Options.Events.CreatingTicket(context);

            if (context.Principal?.Identity == null)
            {
                return(null);
            }

            return(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme));
        }
コード例 #6
0
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
        {
            var tokenRequestParameters = new Dictionary <string, string>()
            {
                { "client_id", Options.ClientId },
                { "redirect_uri", redirectUri },
                { "client_secret", Options.ClientSecret },
                { "code", code }
            };

            var requestContent = new FormUrlEncodedContent(tokenRequestParameters);

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);

            requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            requestMessage.Content = requestContent;
            var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);

            if (response.IsSuccessStatusCode)
            {
                var payloadObject = JObject.Parse(await response.Content.ReadAsStringAsync());
                var payload       = new JObject
                {
                    ["access_token"]  = payloadObject.Property("access_token").Value["token"],
                    ["token_type"]    = "code",
                    ["refresh_token"] = payloadObject.Property("access_token").Value["token"],
                    ["expires_in"]    = payloadObject.Property("access_token").Value["expires_at"]
                };

                return(OAuthTokenResponse.Success(payload));
            }
            else
            {
                var error = new StringBuilder();
                error.Append("OAuth token endpoint failure: ");
                error.Append("Status: " + response.StatusCode + ";");
                error.Append("Headers: " + response.Headers.ToString() + ";");
                error.Append("Body: " + await response.Content.ReadAsStringAsync() + ";");
                return(OAuthTokenResponse.Failed(new Exception(error.ToString())));
            }
        }
コード例 #7
0
    /// <inheritdoc />
    protected override async Task <AuthenticationTicket> CreateTicketAsync(
        [NotNull] ClaimsIdentity identity,
        [NotNull] AuthenticationProperties properties,
        [NotNull] OAuthTokenResponse tokens)
    {
        var contextId = await ProcessIdTokenAndGetContactIdentifierAsync(tokens, properties, identity);

        if (string.IsNullOrEmpty(contextId))
        {
            throw new InvalidOperationException("An error occurred trying to obtain the context identifier from the current user's identity claims.");
        }

        // Add contextId to the Options.UserInformationEndpoint (https://sod.superoffice.com/{0}/api/v1/user/currentPrincipal).
        var userInfoEndpoint = string.Format(CultureInfo.InvariantCulture, Options.UserInformationEndpoint, contextId);

        // Get the SuperOffice user principal.
        using var request = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted);

            throw new HttpRequestException($"An error occurred when retrieving SuperOffice user information ({response.StatusCode}).");
        }

        using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

        var principal = new ClaimsPrincipal(identity);

        var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

        context.RunClaimActions();

        await Events.CreatingTicket(context);

        return(new AuthenticationTicket(context.Principal !, context.Properties, Scheme.Name));
    }
コード例 #8
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, "access_token", tokens.AccessToken);

            if (Options.UseSignedRequests)
            {
                // Compute the HMAC256 signature.
                var signature = ComputeSignature(address);

                // Add the signature to the query string.
                address = QueryHelpers.AddQueryString(address, "sig", signature);
            }

            using var request = new HttpRequestMessage(HttpMethod.Get, address);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                throw new HttpRequestException("An error occurred while retrieving the user profile.");
            }

            using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());

            var principal = new ClaimsPrincipal(identity);
            var context   = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

            context.RunClaimActions(payload.RootElement.GetProperty("data"));

            await Options.Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
        }
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            HttpRequestMessage  request  = null;
            HttpResponseMessage response = null;

            try
            {
                request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

                response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

                if (!response.IsSuccessStatusCode)
                {
                    Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
                                    "returned a {Status} response with the following payload: {Headers} {Body}.",
                                    /* Status: */ response.StatusCode,
                                    /* Headers: */ response.Headers.ToString(),
                                    /* Body: */ await response.Content.ReadAsStringAsync());

                    throw new HttpRequestException("An error occurred while retrieving the user profile.");
                }

                var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

                var principal = new ClaimsPrincipal(identity);
                var context   = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload);
                context.RunClaimActions(payload);

                await Options.Events.CreatingTicket(context);

                return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
            }
            finally
            {
                request?.Dispose();
                response?.Dispose();
            }
        }
コード例 #10
0
        protected virtual async Task <string> GetEmailAsync([NotNull] OAuthTokenResponse tokens)
        {
            // See http://open.weibo.com/wiki/2/account/profile/email for more information about the /account/profile/email.json endpoint.
            var address = QueryHelpers.AddQueryString(Options.UserEmailsEndpoint, new Dictionary <string, string>
            {
                ["access_token"] = tokens.AccessToken
            });

            HttpRequestMessage  request  = null;
            HttpResponseMessage response = null;

            try
            {
                request = new HttpRequestMessage(HttpMethod.Get, address);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Failed requests shouldn't cause an error: in this case, return null to indicate that the email address cannot be retrieved.
                response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

                if (!response.IsSuccessStatusCode)
                {
                    Logger.LogWarning("An error occurred while retrieving the email address associated with the logged in user: "******"the remote server returned a {Status} response with the following payload: {Headers} {Body}.",
                                      /* Status: */ response.StatusCode,
                                      /* Headers: */ response.Headers.ToString(),
                                      /* Body: */ await response.Content.ReadAsStringAsync());

                    return(null);
                }

                var payload = JArray.Parse(await response.Content.ReadAsStringAsync());

                return((from email in payload.AsJEnumerable()
                        select email.Value <string>("email")).FirstOrDefault());
            }
            finally
            {
                request?.Dispose();
                response?.Dispose();
            }
        }
コード例 #11
0
    protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var parameters = new Dictionary <string, string>
        {
            ["client_id"]     = Options.ClientId,
            ["redirect_uri"]  = context.RedirectUri,
            ["client_secret"] = Options.ClientSecret,
            ["code"]          = context.Code,
            ["grant_type"]    = "authorization_code"
        };

        request.Content = new FormUrlEncodedContent(parameters !);

        using var response = await Backchannel.SendAsync(request, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.ExchangeCodeErrorAsync(Logger, response, Context.RequestAborted);

            return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
        }

        // Note: Yammer doesn't return a standard OAuth2 response. To make this middleware compatible
        // with the OAuth2 generic middleware, a compliant JSON payload is generated manually.
        // See https://developer.yammer.com/docs/oauth-2 for more information about this process.
        using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
        string?accessToken = payload.RootElement.GetProperty("access_token").GetString("token");

        var token = new
        {
            access_token  = accessToken,
            token_type    = string.Empty,
            refresh_token = string.Empty,
            expires_in    = string.Empty,
        };

        return(OAuthTokenResponse.Success(JsonSerializer.SerializeToDocument(token)));
    }
コード例 #12
0
    protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
    {
        var tokenRequestParameters = new Dictionary <string, string>()
        {
            ["client_id"]     = Options.ClientId,
            ["redirect_uri"]  = context.RedirectUri,
            ["client_secret"] = Options.ClientSecret,
            ["code"]          = context.Code,
            ["grant_type"]    = "authorization_code",
        };

        // PKCE https://tools.ietf.org/html/rfc7636#section-4.5
        if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier))
        {
            tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier !);
            context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey);
        }

        string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(
                                                        string.Concat(
                                                            Uri.EscapeDataString(Options.ClientId),
                                                            ":",
                                                            Uri.EscapeDataString(Options.ClientSecret))));

        using var requestContent = new FormUrlEncodedContent(tokenRequestParameters !);
        using var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);

        requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
        requestMessage.Content = requestContent;
        requestMessage.Version = Backchannel.DefaultRequestVersion;

        using var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);

        var body = await response.Content.ReadAsStringAsync();

        return(response.IsSuccessStatusCode switch
        {
            true => OAuthTokenResponse.Success(JsonDocument.Parse(body)),
            false => PrepareFailedOAuthTokenReponse(response, body)
        });
        private async Task <string> GetUserIdentifierAsync(OAuthTokenResponse tokens)
        {
            var address = QueryHelpers.AddQueryString(Options.UserIdentificationEndpoint, "access_token", tokens.AccessToken);
            HttpRequestMessage  request  = null;
            HttpResponseMessage response = null;

            try
            {
                request = new HttpRequestMessage(HttpMethod.Get, address);

                response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

                if (!response.IsSuccessStatusCode)
                {
                    Logger.LogError("An error occurred while retrieving the user identifier: the remote server " +
                                    "returned a {Status} response with the following payload: {Headers} {Body}.",
                                    /* Status: */ response.StatusCode,
                                    /* Headers: */ response.Headers.ToString(),
                                    /* Body: */ await response.Content.ReadAsStringAsync());

                    throw new HttpRequestException("An error occurred while retrieving the user identifier.");
                }

                var body = await response.Content.ReadAsStringAsync();

                var index = body.IndexOf("{");
                if (index > 0)
                {
                    body = body.Substring(index, body.LastIndexOf("}") - index + 1);
                }

                var payload = JObject.Parse(body);

                return(payload.Value <string>("openid"));
            }
            finally
            {
                request?.Dispose();
                response?.Dispose();
            }
        }
コード例 #14
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            // See https://developer.foursquare.com/overview/versioning
            // for more information about the mandatory "v" and "m" parameters.
            var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary <string, string> {
                ["m"]           = "foursquare",
                ["v"]           = Options.ApiVersion,
                ["oauth_token"] = tokens.AccessToken,
            });

            var request  = new HttpRequestMessage(HttpMethod.Get, address);
            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            response.EnsureSuccessStatusCode();

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            identity.AddOptionalClaim(ClaimTypes.NameIdentifier, FoursquareAuthenticationHelper.GetIdentifier(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Surname, FoursquareAuthenticationHelper.GetLastName(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.GivenName, FoursquareAuthenticationHelper.GetFirstName(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Name, FoursquareAuthenticationHelper.GetUserName(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Gender, FoursquareAuthenticationHelper.GetGender(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Email, FoursquareAuthenticationHelper.GetContactEmail(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Uri, FoursquareAuthenticationHelper.GetCanonicalUrl(payload), Options.ClaimsIssuer);

            var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens, payload)
            {
                Principal  = new ClaimsPrincipal(identity),
                Properties = properties
            };

            await Options.Events.CreatingTicket(context);

            if (context.Principal?.Identity == null)
            {
                return(null);
            }

            return(new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme));
        }
コード例 #15
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync(
            [NotNull] ClaimsIdentity identity,
            [NotNull] AuthenticationProperties properties,
            [NotNull] OAuthTokenResponse tokens)
        {
            // Note: the ArcGIS API doesn't support content negotiation via headers.
            string address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary <string, string>
            {
                ["f"]     = "json",
                ["token"] = tokens.AccessToken
            });

            using var request = new HttpRequestMessage(HttpMethod.Get, address);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Request the token
            using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());

            // Note: error responses always return 200 status codes.
            if (payload.RootElement.TryGetProperty("error", out var error))
            {
                // See https://developers.arcgis.com/authentication/server-based-user-logins/ for more information
                Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
                                "returned a response with the following error code: {Code} {Message}.",
                                /* Code: */ error.GetString("code"),
                                /* Message: */ error.GetString("message"));

                throw new InvalidOperationException("An error occurred while retrieving the user profile.");
            }

            var principal = new ClaimsPrincipal(identity);
            var context   = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

            context.RunClaimActions();

            await Options.Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
        }
コード例 #16
0
    protected override async Task <AuthenticationTicket> CreateTicketAsync(
        [NotNull] ClaimsIdentity identity,
        [NotNull] AuthenticationProperties properties,
        [NotNull] OAuthTokenResponse tokens)
    {
        string endpoint = Options.UserInformationEndpoint;

        if (Options.Fields.Count > 0)
        {
            endpoint = QueryHelpers.AddQueryString(endpoint, "fields[user]", string.Join(',', Options.Fields));
        }

        if (Options.Includes.Count > 0)
        {
            endpoint = QueryHelpers.AddQueryString(endpoint, "include", string.Join(',', Options.Includes));
        }

        using var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted);

            throw new HttpRequestException("An error occurred while retrieving the user profile.");
        }

        using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

        var principal = new ClaimsPrincipal(identity);
        var context   = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

        context.RunClaimActions(payload.RootElement.GetProperty("data"));

        await Events.CreatingTicket(context);

        return(new AuthenticationTicket(context.Principal !, context.Properties, Scheme.Name));
    }
        /// <inheritdoc/>
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
            request.Headers.UserAgent.ParseAdd(OneIdAuthenticationDefaults.UserAgent);

            var parameters = new Dictionary <string, string>
            {
                ["redirect_uri"]  = context.RedirectUri,
                ["grant_type"]    = "authorization_code",
                ["client_id"]     = Options.ClientId,
                ["code"]          = context.Code,
                ["code_verifier"] = context.Properties.Items["code_verifier"]
            };

            request.Content = new FormUrlEncodedContent(parameters);

            using var response = await Backchannel.SendAsync(request, Context.RequestAborted).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                string errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                Logger.LogError("An error occurred while retrieving an access token: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ errorBody);

                return(OAuthTokenResponse.Failed(new OneIdAuthenticationException($"An error occurred while retrieving an access token. The remote server returned a {response.StatusCode} response with the following payload: {response.Headers.ToString()} {errorBody}")));
            }

            var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false));

            return(OAuthTokenResponse.Success(payload));
        }
コード例 #18
0
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
        {
            var tokenRequestParameters = new Dictionary <string, string?>()
            {
                ["app_id"] = Options.ClientId,
                ["secret"] = Options.ClientSecret,
                ["code"]   = context.Code,
                ["output"] = "json",
            };

            // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
            if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out string?codeVerifier))
            {
                tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier);
                context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey);
            }

            string endpoint = QueryHelpers.AddQueryString(Options.TokenEndpoint, tokenRequestParameters);

            using var requestMessage = new HttpRequestMessage(HttpMethod.Get, endpoint);
            requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
            using var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                const string Error = "An error occurred while retrieving an OAuth token";

                Logger.LogError("{Error}: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Error: */ Error,
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync(Context.RequestAborted));

                return(OAuthTokenResponse.Failed(new Exception(Error)));
            }

            var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

            return(OAuthTokenResponse.Success(payload));
        }
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] string code, [NotNull] string redirectUri)
        {
            var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary <string, string>()
            {
                ["client_id"]     = Options.ClientId,
                ["client_secret"] = Options.ClientSecret,
                ["redirect_uri"]  = redirectUri,
                ["code"]          = code,
                ["grant_type"]    = "authorization_code",
            });

            HttpRequestMessage  request  = null;
            HttpResponseMessage response = null;

            try
            {
                request  = new HttpRequestMessage(HttpMethod.Get, address);
                response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

                if (!response.IsSuccessStatusCode)
                {
                    Logger.LogError("An error occurred while retrieving an access token: the remote server " +
                                    "returned a {Status} response with the following payload: {Headers} {Body}.",
                                    /* Status: */ response.StatusCode,
                                    /* Headers: */ response.Headers.ToString(),
                                    /* Body: */ await response.Content.ReadAsStringAsync());

                    return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
                }

                var payload = JObject.FromObject(QueryHelpers.ParseQuery(await response.Content.ReadAsStringAsync())
                                                 .ToDictionary(pair => pair.Key, k => k.Value.ToString()));

                return(OAuthTokenResponse.Success(payload));
            }
            finally
            {
                request?.Dispose();
                response?.Dispose();
            }
        }
コード例 #20
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
        {
            var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

            var response = await Backchannel.SendAsync(request, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException($"An error occurred when retrieving Microsoft user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Microsoft Account API is enabled.");
            }

            using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
            var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

            context.RunClaimActions();
            await Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal !, context.Properties, Scheme.Name));
        }
コード例 #21
0
        /// <summary>
        /// Performs a backchannel request to obtain user information from the specified endpoint
        /// </summary>
        /// <param name="endpoint">Endpoint to return information from</param>
        /// <param name="accessToken">Bearer token for authentication</param>
        /// <param name="requestInformationType">Descriptive text of type of information request in event of exception</param>
        /// <returns>Parsed JSON document response from endpoint</returns>
        private async Task <JsonDocument> RequestUserInformationAsync(string endpoint, string accessToken, string requestInformationType)
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError($"An error occurred while retrieving the {requestInformationType}: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync(Context.RequestAborted));

                throw new HttpRequestException($"An error occurred while retrieving the {requestInformationType}.");
            }

            return(JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted)));
        }
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] string code, [NotNull] string redirectUri)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint)
            {
                Content = new FormUrlEncodedContent(new Dictionary <string, string>
                {
                    ["client_id"]     = Options.ClientId,
                    ["redirect_uri"]  = redirectUri,
                    ["client_secret"] = Options.ClientSecret,
                    ["code"]          = code,
                    ["grant_type"]    = "authorization_code"
                })
            };

            var response = await Backchannel.SendAsync(request, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving an access token: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
            }

            // Note: StackExchange's token endpoint doesn't return JSON but uses application/x-www-form-urlencoded.
            // Since OAuthTokenResponse expects a JSON payload, a response is manually created using the returned values.
            var content = QueryHelpers.ParseQuery(await response.Content.ReadAsStringAsync());

            var payload = new JObject();

            foreach (var item in content)
            {
                payload[item.Key] = (string)item.Value;
            }

            return(OAuthTokenResponse.Success(payload));
        }
コード例 #23
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync(
            ClaimsIdentity identity,
            Microsoft.AspNetCore.Authentication.AuthenticationProperties properties,
            OAuthTokenResponse tokens)
        {
            var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);

            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);

            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("Произошла ошибка при получении профиля пользователя: удаленный сервер " +
                                "вернул {Status} ответ со следующей информацией: {Headers} {Body}.",
                                response.StatusCode,
                                response.Headers.ToString(),
                                await response.Content.ReadAsStringAsync());

                throw new HttpRequestException("Произошла ошибка при получении профиля пользователя.");
            }

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            identity.AddOptionalClaim(ClaimTypes.NameIdentifier, payload.Value <string>("id"), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Name, payload.Value <string>("login"), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Surname, payload.Value <string>("last_name"), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.GivenName, payload.Value <string>("first_name"), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Email, payload.Value <JArray>("emails")?[0]?.Value <string>(), Options.ClaimsIssuer);


            var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload);

            context.RunClaimActions();

            await Options.Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
        }
コード例 #24
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            // Note: unlike the other social providers, the userinfo endpoint is user-specific and can't be set globally.
            // For more information, see https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com
            var request = new HttpRequestMessage(HttpMethod.Get, tokens.Response.Value <string>("id"));

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred when retrieving the user profile: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                throw new HttpRequestException("An error occurred when retrieving the user from the Salesforce identity service.");
            }

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            identity.AddOptionalClaim(ClaimTypes.NameIdentifier, SalesforceAuthenticationHelper.GetUserIdentifier(payload), Options.ClaimsIssuer)
            .AddOptionalClaim(ClaimTypes.Name, SalesforceAuthenticationHelper.GetUserName(payload), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:salesforce:email", SalesforceAuthenticationHelper.GetEmail(payload), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:salesforce:thumbnail_photo", SalesforceAuthenticationHelper.GetThumbnailPhoto(payload), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:salesforce:utc_offset", SalesforceAuthenticationHelper.GetUtcOffset(payload).ToString(), Options.ClaimsIssuer)
            .AddOptionalClaim("urn:salesforce:rest_url", SalesforceAuthenticationHelper.GetRestUrl(payload), Options.ClaimsIssuer);

            var principal = new ClaimsPrincipal(identity);
            var ticket    = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);

            var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, payload);
            await Options.Events.CreatingTicket(context);

            return(context.Ticket);
        }
    private async Task <(int ErrorCode, string?OpenId, string?UnionId)> GetUserIdentifierAsync(OAuthTokenResponse tokens)
    {
        // See https://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D for details
        var queryString = new Dictionary <string, string?>(3)
        {
            ["access_token"] = tokens.AccessToken,
            ["fmt"]          = "json" // Return JSON instead of JSONP which is default due to historical reasons
        };

        if (Options.ApplyForUnionId)
        {
            queryString.Add("unionid", "1");
        }

        string address = QueryHelpers.AddQueryString(Options.UserIdentificationEndpoint, queryString);

        using var request = new HttpRequestMessage(HttpMethod.Get, address);

        using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

        if (!response.IsSuccessStatusCode)
        {
            await Log.UserIdentifierErrorAsync(Logger, response, Context.RequestAborted);

            throw new HttpRequestException("An error occurred while retrieving the user identifier.");
        }

        using var stream = await response.Content.ReadAsStreamAsync(Context.RequestAborted);

        using JsonDocument payload = JsonDocument.Parse(stream);

        var payloadRoot = payload.RootElement;

        int errorCode =
            payloadRoot.TryGetProperty("error", out var errorCodeElement) && errorCodeElement.ValueKind == JsonValueKind.Number ?
            errorCodeElement.GetInt32() :
            0;

        return(errorCode, payloadRoot.GetString("openid"), payloadRoot.GetString("unionid"));
    }
コード例 #26
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync(
            [NotNull] ClaimsIdentity identity,
            [NotNull] AuthenticationProperties properties,
            [NotNull] OAuthTokenResponse tokens)
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", tokens.AccessToken);

            // When a custom user agent is specified in the options, add it to the request headers
            // to override the default (generic) user agent used by the OAuth2 base middleware.
            if (!string.IsNullOrEmpty(Options.UserAgent))
            {
                request.Headers.UserAgent.ParseAdd(Options.UserAgent);
            }

            using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync(Context.RequestAborted));

                throw new HttpRequestException("An error occurred while retrieving the user profile.");
            }

            using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));

            var principal = new ClaimsPrincipal(identity);
            var context   = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);

            context.RunClaimActions();

            await Options.Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal !, context.Properties, Scheme.Name));
        }
コード例 #27
0
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context)
        {
            // for epic, must pass in list of scopes in tokenRequest form body,
            // AND they must match the permissions list in the Application Permissions
            // EXACTLY, or you get errors about how you don't have permission to view
            // the scope. Even if you're asking for LESS scope.
            var tokenRequestParameters = new Dictionary <string, string>()
            {
                { "redirect_uri", context.RedirectUri },
                { "code", context.Code },
                { "grant_type", "authorization_code" },
                { "scopes", string.Join(" ", Options.Scope) } // <-- important!
            };

            var requestContent = new FormUrlEncodedContent(tokenRequestParameters);

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);

            requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Epic requires passing in ClientId and ClientSecret via Authorization Header,
            // instead of in the form body.
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic",
                                                                                 Convert.ToBase64String(Encoding.UTF8.GetBytes(Options.ClientId + ":" + Options.ClientSecret)));

            requestMessage.Content = requestContent;
            var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);

            if (response.IsSuccessStatusCode)
            {
                var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
                return(OAuthTokenResponse.Success(payload));
            }
            else
            {
                var error = "OAuth token endpoint failure: " + await Display(response);

                return(OAuthTokenResponse.Failed(new Exception(error)));
            }
        }
コード例 #28
0
        protected override async Task <AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
                                                                               [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
        {
            var userId  = tokens.Response.SelectToken("user").Value <string>("id");
            var request = new HttpRequestMessage(HttpMethod.Get, $"{Options.UserInformationEndpoint}api/v1/users/{userId}/profile");

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                throw new HttpRequestException("An error occurred while retrieving the user from the Canvas identity service.");
            }

            var payload   = JObject.Parse(await response.Content.ReadAsStringAsync());
            var principal = new ClaimsPrincipal(identity);

            var roles = await GetAccountRolesForUserAsync(Backchannel, "1", userId, tokens.AccessToken);

            foreach (var role in roles)
            {
                identity.AddClaim(new Claim(ClaimTypes.Role, role));
            }

            var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload);

            context.RunClaimActions(payload);

            await Options.Events.CreatingTicket(context);

            return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
        }
コード例 #29
0
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
        {
            var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Options.ClientId}:{Options.ClientSecret}"));

            var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);

            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);

            // When a custom user agent is specified in the options, add it to the request headers
            // to override the default (generic) user agent used by the OAuth2 base middleware.
            if (!string.IsNullOrEmpty(Options.UserAgent))
            {
                request.Headers.UserAgent.ParseAdd(Options.UserAgent);
            }

            request.Content = new FormUrlEncodedContent(new Dictionary <string, string>
            {
                ["grant_type"]   = "authorization_code",
                ["redirect_uri"] = redirectUri,
                ["code"]         = code
            });

            var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving an access token: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
            }

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            return(OAuthTokenResponse.Success(payload));
        }
        protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] string code, [NotNull] string redirectUri)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);

            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            request.Content = new FormUrlEncodedContent(new Dictionary <string, string>
            {
                ["client_id"]     = Options.ClientId,
                ["redirect_uri"]  = redirectUri,
                ["client_secret"] = Options.ClientSecret,
                ["code"]          = code,
                ["grant_type"]    = "authorization_code"
            });

            var response = await Backchannel.SendAsync(request, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("An error occurred while retrieving an access token: the remote server " +
                                "returned a {Status} response with the following payload: {Headers} {Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ await response.Content.ReadAsStringAsync());

                return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")));
            }

            // Note: Yammer doesn't return a standard OAuth2 response. To make this middleware compatible
            // with the OAuth2 generic middleware, a compliant JSON payload is generated manually.
            // See https://developer.yammer.com/docs/oauth-2 for more information about this process.
            var payload = JObject.Parse(await response.Content.ReadAsStringAsync())["access_token"].Value <JObject>();

            payload["access_token"]  = payload["token"];
            payload["token_type"]    = string.Empty;
            payload["refresh_token"] = string.Empty;
            payload["expires_in"]    = string.Empty;

            return(OAuthTokenResponse.Success(payload));
        }