Skip to content

Instantly share code, notes, and snippets.

@promontis
Last active August 2, 2016 11:42
Show Gist options
  • Save promontis/0f05a963491f9cf352b2 to your computer and use it in GitHub Desktop.
Save promontis/0f05a963491f9cf352b2 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Facebook;
using Newtonsoft.Json.Linq;
using Thinktecture.IdentityServer.Core.Logging;
using Thinktecture.IdentityServer.Core.Models;
using Thinktecture.IdentityServer.Core.Services;
using Thinktecture.IdentityServer.Core.Validation;
namespace Shoegle.Auth.Config
{
public class FacebookConnectGrantValidator : ICustomGrantValidator
{
private readonly IUserService _userService;
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string GraphApiEndpoint = "https://graph.facebook.com/me";
private readonly static ILog Logger = LogProvider.GetCurrentClassLogger();
private readonly IExternalClaimsFilter _externalClaimsFilter;
public FacebookConnectOptions Options { get; set; }
public FacebookConnectGrantValidator(FacebookConnectOptions options, IUserService userService, IExternalClaimsFilter externalClaimsFilter)
{
_userService = userService;
_externalClaimsFilter = externalClaimsFilter;
Options = options;
}
private ExternalIdentity MapToExternalIdentity(IEnumerable<Claim> claims)
{
var externalId = ExternalIdentity.FromClaims(claims);
if (externalId != null && _externalClaimsFilter != null)
{
externalId.Claims = _externalClaimsFilter.Filter(externalId.Provider, externalId.Claims);
}
return externalId;
}
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
if (request.GrantType == "facebook_connect")
{
var accessToken = request.Raw["assertion"];
using (var httpClient = new HttpClient())
{
var graphAddress = GraphApiEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken);
var graphResponse = await httpClient.GetAsync(graphAddress);
graphResponse.EnsureSuccessStatusCode();
var text = await graphResponse.Content.ReadAsStringAsync();
var user = JObject.Parse(text);
var context = new FacebookAuthenticatedContext(null, user, accessToken, "");
context.Identity = new ClaimsIdentity(
Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if (!string.IsNullOrEmpty(context.Id))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString,
Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.UserName))
{
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName,
XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString,
Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Name))
{
context.Identity.AddClaim(new Claim("urn:facebook:name", context.Name, XmlSchemaString,
Options.AuthenticationType));
// Many Facebook accounts do not set the UserName field. Fall back to the Name field instead.
if (string.IsNullOrEmpty(context.UserName))
{
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name,
XmlSchemaString, Options.AuthenticationType));
}
}
if (!string.IsNullOrEmpty(context.Link))
{
context.Identity.AddClaim(new Claim("urn:facebook:link", context.Link, XmlSchemaString,
Options.AuthenticationType));
}
await Options.Provider.Authenticated(context);
var externalIdentity = MapToExternalIdentity(context.Identity.Claims);
if (externalIdentity == null)
{
Logger.Error("no subject or unique identifier claims from external identity provider");
}
Logger.InfoFormat("external user provider: {0}, provider ID: {1}", externalIdentity.Provider,
externalIdentity.ProviderId);
var signInMessage = new SignInMessage {ClientId = request.Client.ClientId};
var authResult = await _userService.AuthenticateExternalAsync(externalIdentity, signInMessage);
var result = new CustomGrantValidationResult { Principal = authResult.User };
return result;
//return IdentityServerPrincipal.Create(authResult.User.S, authResult.Name, "external", authResult.Provider);
}
}
return null;
}
}
}
using Microsoft.Owin.Security.Facebook;
namespace Shoegle.Auth.Config
{
public class FacebookConnectOptions
{
public string AppToken { get; set; }
public string AuthenticationType { get; set; }
public IFacebookAuthenticationProvider Provider { get; set; }
}
}
using System.Linq;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
namespace Shoegle.Auth.Config
{
public class FacebookIdentityNormalizer
{
public static void Normalize(string accessToken, JObject user, ClaimsIdentity claimsIdentity)
{
claimsIdentity.AddClaim(new Claim("urn:facebook:accesstoken", accessToken));
foreach (var claim in user)
{
var claimType = string.Format("urn:facebook:{0}", claim.Key);
var claimValue = claim.Value.ToString();
if (!claimsIdentity.HasClaim(claimType, claimValue))
{
claimsIdentity.AddClaim(new Claim(claimType, claimValue));
}
}
// Normalize the profile
var facebookId = claimsIdentity.Claims.Single(x => x.Type == "urn:facebook:id");
claimsIdentity.AddClaim(new Claim("urn:shoegler:photo",
string.Format("https://graph.facebook.com/{0}/picture?type=large", facebookId.Value)));
var facebookName = claimsIdentity.Claims.Single(x => x.Type == "urn:facebook:name");
claimsIdentity.AddClaim(new Claim("urn:shoegler:name", facebookName.Value));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment