Skip to content
This repository has been archived by the owner on Sep 18, 2021. It is now read-only.

Reach claims from external provider in OpenIdConnectAuthenticationNotifications #821

Closed
DMDc0de opened this issue Jan 26, 2015 · 4 comments
Assignees
Labels

Comments

@DMDc0de
Copy link

DMDc0de commented Jan 26, 2015

Hello, I'm trying to follow your 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 foreach loop I', 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 Identites?
Is there a right way to achieve what I am trying to do?
Thank you.

@leastprivilege
Copy link
Member

I assume the upper code is in idsrv - the lower is your app?

@DMDc0de
Copy link
Author

DMDc0de commented Jan 26, 2015

Right now it's all in the Startup class of the project that holds a self hosted idsrv. This is the full class

public class Startup {

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

//            app.SetDefaultSignInAsAuthenticationType(OpenIdConnectAuthenticationDefaults.AuthenticationType);
            var factory = InMemoryFactory.Create(
                users: Users.Get(),
                clients: Clients.Get(),
                scopes: Scopes.Get());
//            factory.ClaimsProvider = new Registration<IClaimsProvider>(resolver => new CustomClaimsProvider());

            app.Map("/identity", idsrvApp => idsrvApp.UseIdentityServer(new IdentityServerOptions {
                SiteName = "IPPS Web Public IdentityServer",
                SigningCertificate = LoadCertificate(),
                Factory = factory,
                AuthenticationOptions = new Thinktecture.IdentityServer.Core.Configuration.AuthenticationOptions() {
                    IdentityProviders = ConfigureIdentityProviders
                }
            }));


            app.UseCookieAuthentication(new CookieAuthenticationOptions() {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions() {
                Authority = "https://localhost:44302/identity/",
                ClientId = "ipps.web.public",
                Scope = "openid profile roles email publicAPI",
                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);


                        // get userinfo data
                        var userInfoClient = new Thinktecture.IdentityModel.Client.UserInfoClient(
                            new Uri(context.Options.Authority + "/connect/userinfo"),
                            context.ProtocolMessage.AccessToken);

                        var userInfo = await userInfoClient.GetAsync();
                        userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));

                        // keep the id_token for logout
                        nid.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));

                        // add access token for IPPS API
                        nid.AddClaim(new Claim("access_token", context.ProtocolMessage.AccessToken));

                        // keep track of access token expiration
                        nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(context.ProtocolMessage.ExpiresIn)).ToString()));

                        // add some other app specific claim
//                        nid.AddClaim(new Claim("app_specific", "some data"));

                        context.AuthenticationTicket = new AuthenticationTicket(
                            nid,
                            context.AuthenticationTicket.Properties);
                    },
                    RedirectToIdentityProvider = async f => {
                        //need to populate the redirect link hint
                        if (f.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest) {
                            String idTokenHint = f.OwinContext.Authentication.User.FindFirst("id_token").Value;
                            f.ProtocolMessage.IdTokenHint = idTokenHint;
                        }
                    }
                }
            });

            app.UseResourceAuthorization(new AuthorizationManager());
        }

        //google external login authorization
        private void ConfigureIdentityProviders(IAppBuilder app, String signInAsType) {
            app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() {
                AuthenticationType = "Google",
                Caption = "Sign in with Google",
                SignInAsAuthenticationType = signInAsType,
                ClientId = "*********",
                ClientSecret = "***********",
            });

            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);
        }


        private X509Certificate2 LoadCertificate() {
            return new X509Certificate2(
                string.Format(@"{0}\bin\identityServer\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
        }

    }

I'm quite a noobie with authentication and authorization, I hope I haven't misunderstood the guide...

@leastprivilege
Copy link
Member

As I wrote in the walkthrough - putting idsrv and the client in the same application is just an option for development time - you might find it easier to separate them (to make things clearer what belongs where).

wrt your question - to "copy" claims from facebook to the idsrv identity you need to use the idsrv user service - the in-memory one does that (again only suitable for testing). Maybe familiarize yourself with the source code of the InMemoryUserService first (AuthenticateExternalAsync in particular).

@DMDc0de
Copy link
Author

DMDc0de commented Jan 26, 2015

Of course I'm just testing, and yes I'm using the In-Memory users, once I'm fluent with concept and api I'll surely begin to divide and organize the code, I just need to understand first if what I need to do can be implemented and how :)
Alright so I'll head towards the InMemoryUserService. Thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants