Daniele
Daniele

Reputation: 1063

Thinktecture Identity Server v3 How to keep Claims from external providers?

I'm trying to follow the simple guide mvcGettingStarted. Now, I've implemented both GoogleAuthentication and FacebookAuthentication providers, and everything is working as expected, I actually can log-in, and if I sign in with my identity server I also got the Role claims per user. I was wondering, what if I want to keep all the claims given from the external providers? Simple example. This is how my Facebook provider setup looks like:

var facebookOptions = new FacebookAuthenticationOptions() {
            AuthenticationType = "Facebook",
            Caption = "Sign in with Facebook",
            AppId = "*****",
            AppSecret = "****",
            SignInAsAuthenticationType = signInAsType,
            Provider = new FacebookAuthenticationProvider() {
                OnAuthenticated = (context) => {

                    foreach (var x in context.User) {
                        context.Identity.AddClaim(new Claim(x.Key, x.Value.ToString()));
                    }

                    return Task.FromResult(context);
                }
            },
        };

        facebookOptions.Scope.Add("email");
        facebookOptions.Scope.Add("public_profile");
        facebookOptions.Scope.Add("user_friends");

        app.UseFacebookAuthentication(facebookOptions);

In the for each loop I'm trying to store all the Facebook claims in the Identity, but when I get back in the SecurityTokenValidated callback, my Identity hasn't them.

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions() {
            Authority = "https://localhost:44302/identity/",
            ClientId = "my_client",
            Scope = "openid profile roles email",
            RedirectUri = "https://localhost:44302/",
            ResponseType = "id_token token",
            SignInAsAuthenticationType = "Cookies",
            UseTokenLifetime = false,
            Notifications = new OpenIdConnectAuthenticationNotifications() {

                SecurityTokenValidated = async context => {
                    //let's clean up this identity

                    //context.AuthenticationTicket.Identity doesn't have the claims added in the facebook callback
                    var nid = new ClaimsIdentity(
                        context.AuthenticationTicket.Identity.AuthenticationType,
                        Constants.ClaimTypes.GivenName,
                        Constants.ClaimTypes.Role);
                    ........

Is it because I'm manipulating two different Identities? Is there a right way to achieve what I am trying to do? Thank you.

Upvotes: 6

Views: 7883

Answers (2)

Brock Allen
Brock Allen

Reputation: 7435

You would do this in your custom user service implementation. The default one makes the claims from the external provider available. Docs on a custom user service: https://identityserver.github.io/Documentation/docsv2/advanced/userService.html

Upvotes: 4

Daniele
Daniele

Reputation: 1063

As @brock-allen said, the user service is the right path to follow. So I went on and implemented a simple UserService

public class UserService {
    private static InMemoryUserService _service = null;
    public static InMemoryUserService Get() {
        if(_service == null)
            _service = new InMemoryUserService(Users.Get());

        return _service;
    }
}

registered my userservice in my factory like this

public void Configuration(IAppBuilder app) {
        AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
        JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

        var factory = InMemoryFactory.Create(
            users: Users.Get(),
            clients: Clients.Get(),
            scopes: Scopes.Get());
        factory.UserService = new Registration<IUserService>(resolver => UserService.Get());

.....

(Of course that's the Configuration method inside my Startup class)

So now I can authenticate the external user inside the authentication callback of the external provider (in this case facebook), specifing all the claims that I need:

var facebookOptions = new FacebookAuthenticationOptions() {
            AuthenticationType = "Facebook",
            Caption = "Sign in with Facebook",
            AppId = "******",
            AppSecret = "*******",
            SignInAsAuthenticationType = signInAsType,
            Provider = new FacebookAuthenticationProvider() {
                OnAuthenticated = (context) => {

                    foreach (var x in context.User) {
                        context.Identity.AddClaim(new Claim(x.Key, x.Value.ToString()));
                    }

                    ExternalIdentity identity = new ExternalIdentity() {
                        Claims = context.Identity.Claims,
                        Provider = "Facebook",
                        ProviderId = "Facebook"
                    };
                    SignInMessage signInMessage = new SignInMessage();

                    UserService.Get().AuthenticateExternalAsync(identity, signInMessage);


                    return Task.FromResult(context);
                }
            },
        }

Now, I can do

List<Claim> claims = await UserService.Get().GetProfileDataAsync(User as ClaimsPrincipal) as List<Claim>;

And see that my User has all the claims facebook provided during authentication. Of course this code is just for testing purposes, it can be improved a lot.

Upvotes: 6

Related Questions