// 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.Threading.Tasks; using Microsoft.AspNet.Authentication.OAuth; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.WebUtilities; using Newtonsoft.Json.Linq; namespace Microsoft.AspNet.Authentication.Facebook { internal class FacebookHandler : OAuthHandler { public FacebookHandler(HttpClient httpClient) : base(httpClient) { } 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); response.EnsureSuccessStatusCode(); var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Options.AuthenticationScheme); var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, payload); var identifier = FacebookHelper.GetId(payload); if (!string.IsNullOrEmpty(identifier)) { identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer)); } var userName = FacebookHelper.GetUserName(payload); if (!string.IsNullOrEmpty(userName)) { identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, userName, ClaimValueTypes.String, Options.ClaimsIssuer)); } var email = FacebookHelper.GetEmail(payload); if (!string.IsNullOrEmpty(email)) { identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer)); } var name = FacebookHelper.GetName(payload); if (!string.IsNullOrEmpty(name)) { identity.AddClaim(new Claim("urn:facebook:name", name, ClaimValueTypes.String, Options.ClaimsIssuer)); // Many Facebook accounts do not set the UserName field. Fall back to the Name field instead. if (string.IsNullOrEmpty(userName)) { identity.AddClaim(new Claim(identity.NameClaimType, name, ClaimValueTypes.String, Options.ClaimsIssuer)); } } var link = FacebookHelper.GetLink(payload); if (!string.IsNullOrEmpty(link)) { identity.AddClaim(new Claim("urn:facebook:link", link, ClaimValueTypes.String, Options.ClaimsIssuer)); } await Options.Events.CreatingTicket(context); return context.Ticket; } 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); } } }