// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; using System.Net.Http; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.Facebook { public class FacebookHandler : OAuthHandler { public FacebookHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { var endpoint = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, "access_token", tokens.AccessToken); if (Options.SendAppSecretProof) { endpoint = QueryHelpers.AddQueryString(endpoint, "appsecret_proof", GenerateAppSecretProof(tokens.AccessToken)); } if (Options.Fields.Count > 0) { endpoint = QueryHelpers.AddQueryString(endpoint, "fields", string.Join(",", Options.Fields)); } var response = await Backchannel.GetAsync(endpoint, Context.RequestAborted); if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"An error occurred when retrieving Facebook user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Facebook Graph API is enabled."); } var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); context.RunClaimActions(); await Events.CreatingTicket(context); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } private string GenerateAppSecretProof(string accessToken) { using (var algorithm = new HMACSHA256(Encoding.ASCII.GetBytes(Options.AppSecret))) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(accessToken)); var builder = new StringBuilder(); for (int i = 0; i < hash.Length; i++) { builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture)); } return builder.ToString(); } } protected override string FormatScope() { // Facebook deviates from the OAuth spec here. They require comma separated instead of space separated. // https://developers.facebook.com/docs/reference/dialogs/oauth // http://tools.ietf.org/html/rfc6749#section-3.3 return string.Join(",", Options.Scope); } } }