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

2

6

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.

Lahnda answered 26/1, 2015 at 12:40 Comment(0)
A
4

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

Anthropopathy answered 26/1, 2015 at 17:11 Comment(1)
the link is resulting in 404 identityserver.github.io/Documentation/docs/advanced/…Cthrine
L
6

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.

Lahnda answered 27/1, 2015 at 11:7 Comment(4)
I am going through some struggles understanding local vs external claims too. See #28748500 . Are you able to shed any light on this?Chronogram
@Lahnda can you post whole code? I am very interested. ThxEquiprobable
Is the AuthenticateExternalAsync an extension method? I couldn't find one that accepts those args. The one that I got here only accepts an ExternalAuthenticationContext as argument and has no overload.Triumphant
same here @BrunoGrisoliaCasarotti did you find out wher e it comes from?Clubbable
A
4

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

Anthropopathy answered 26/1, 2015 at 17:11 Comment(1)
the link is resulting in 404 identityserver.github.io/Documentation/docs/advanced/…Cthrine

© 2022 - 2024 — McMap. All rights reserved.