waqaskhan540 / identityserver-token-exchange

A solution for exchanging external (Facebook,Google,Twitter etc) tokens with IdentityServer access token.
GNU General Public License v3.0
15 stars 20 forks source link

invalid_grant thrown after adding custom IProfileService #2

Open EricNjue opened 6 years ago

EricNjue commented 6 years ago

How would one add a custom IProfileService service provider to the existing app? Am looking into ways I can integrate local account creation in combination with social login providers.

The issue am experiencing is that, after adding a custom IProfileService, am getting an "invalid_grant" response. Am using the below code

services.AddIdentityServer() .AddDeveloperSigningCredential() .AddAspNetIdentity<ApplicationUser>() //this adds the config data from DB (clients, resources) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseNpgsql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) // this adds the operational data from DB (codes, tokens, consents) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseNpgsql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); // this enables automatic token cleanup. this is optional options.EnableTokenCleanup = true; options.TokenCleanupInterval = 30; }) .AddProfileService<ProfileServices>();

I also would like to add more claims to a user, how would one achieve that?

waqaskhan540 commented 6 years ago

can you please share the request you are sending to the identityserver ?

EricNjue commented 6 years ago

code2.txt code1

I've attached a sample file showing the various clients I've & a sample request done via postman

waqaskhan540 commented 6 years ago

instead of grant_type : "password" , the grant_type should be "external" if you want to exchange external tokens with identityserver..

EricNjue commented 6 years ago

If I put external as the grant_type, would I not be limited to use just the external providers? I'm trying to achieve a solution whereby I can use both External providers (using this project) & also provision of users to create accounts within our app domain. For the external users, its working perfectly, but after adding a custom IProfileService implementation, even the external grant_type stops working and throws invalid_grant. What could be the issue?

waqaskhan540 commented 6 years ago

Let me test it at my end , I will come back to you with further updates regarding your issue..

romulotil commented 6 years ago

any advances on this? i have the same scenario as @EricNjue and i didnt find any way around yet

waqaskhan540 commented 6 years ago

@EricNjue @romulotil Sorry for my late reply , I have been busy in other stuff. I have made a few changes to my CustomProfileService so that I could use both the "external" grant_type and "password" grant..

below is the code for my updated 'CustomProfileService".. What I am doing in it is , I have two different stores (one for external users and the other for the app users), so based on the 'subject' I try to find the user in either of the stores.. If found one the I send the user claims in the context..

` public class MyCustomProfileService : IProfileService { private readonly IExternalUserStore _externalUserStore; private readonly TestUserStore _testUserStore;

    public MyCustomProfileService(IExternalUserStore externalUserStore,TestUserStore testUserStore)
    {
        _externalUserStore = externalUserStore;
        _testUserStore = testUserStore;
    }
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {

        var user = _testUserStore.FindBySubjectId(context.Subject.GetSubjectId());
        if(user != null)
        {
            context.AddRequestedClaims(user.Claims);
        }
        else if (await _externalUserStore.FindByIdAsync(context.Subject.GetSubjectId()) != null)
        {
            var claims = await _externalUserStore.GetUserClaimsByIdAsync(context.Subject.GetSubjectId());
            context.AddRequestedClaims(claims);
        }

    }

    public  Task IsActiveAsync(IsActiveContext context)
    {
        // var subjectid = await _externalUserStore.FindByIdAsync(context.Subject.GetSubjectId());
        context.IsActive = true;
        return Task.CompletedTask;
    }
}`
waqaskhan540 commented 6 years ago

@EricNjue You can add additional claims in your custom Profile Service..

romulotil commented 6 years ago

@waqaskhan540 thanks for replying... It was very helpful

I changed the TestUserStore with the UserStore from EF I am using and now it's working fine.

waqaskhan540 commented 6 years ago

@romulotil Cheers..

adamdsouza1 commented 5 years ago

@romulotil How did you achieve changing the TestUserStore with the UserStore from EF? @waqaskhan540 any advice?

waqaskhan540 commented 5 years ago

@adamdsouza1

If you are using Identity then you can simply replace TestUserStore with UserManager..

adamdsouza1 commented 5 years ago

@waqaskhan540

I am getting the following exception. Could you point me in the right direction?

System.InvalidOperationException: Unable to resolve service for type 'IdentityServer.External.TokenExchange.Interfaces.IExternalUserStore' while attempting to activate 'QuickApp.Pro.Authorization.ProfileService'. at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType) at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at IdentityServer4.Hosting.EndpointRouter.GetEndpointHandler(Endpoint endpoint, HttpContext context) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\EndpointRouter.cs:line 52 at IdentityServer4.Hosting.EndpointRouter.Find(HttpContext context) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\EndpointRouter.cs:line 39 at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerMiddleware.cs:line 49 at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerMiddleware.cs:line 69 at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context) at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\BaseUrlMiddleware.cs:line 36 at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

The implementation of the custom ProfileService is as follows

public class ProfileService : IProfileService { private readonly UserManager _userManager; private readonly IUserClaimsPrincipalFactory _claimsFactory; private readonly IExternalUserStore _externalUserStore; public ProfileService(IExternalUserStore externalUserStore, UserManager userManager, IUserClaimsPrincipalFactory claimsFactory) { _externalUserStore = externalUserStore; _userManager = userManager; _claimsFactory = claimsFactory; }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var sub = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(sub);
        if (user != null)
        {
            var principal = await _claimsFactory.CreateAsync(user);

            var claims = principal.Claims.ToList();
            claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

            if (user.JobTitle != null)
                claims.Add(new Claim(PropertyConstants.JobTitle, user.JobTitle));

            if (user.FullName != null)
                claims.Add(new Claim(PropertyConstants.FullName, user.FullName));

            if (user.Configuration != null)
                claims.Add(new Claim(PropertyConstants.Configuration, user.Configuration));

            context.IssuedClaims = claims;
        }
        else if (await _externalUserStore.FindByIdAsync(sub) != null)
        {
            var claims = await _externalUserStore.GetUserClaimsByIdAsync(sub);
            context.AddRequestedClaims(claims);
        }
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var sub = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(sub);
        if (user != null)
        {
            context.IsActive = (user != null) && user.IsEnabled;
        }
        else if (await _externalUserStore.FindByIdAsync(sub) != null)
        {
            context.IsActive = true;
        }
    }
}
waqaskhan540 commented 5 years ago

@adamdsouza1

I think you need to bind your ProfileService in the DependencyInjection.. services.AddScoped<IProfileService,ProfileService>();