Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map IdentityServer4 Identity to any WebApp (.Net MVC Boilerplate, .Net Core Boilerplate)

I'm creating an SSO server, to centralize all users in ActiveDirectory(AD) and manage them there instead of the database of each specific application.

To made this server I used IdentityServer4(Idsr4) with Ldap/AD Extension

I've setted the Idsr4 to use identity based on AD (this is "centralized identity"), and users now can login on Idsr4 with own AD login/ password

The question now is how to map the centralized identity to applications. I want to use same identity user in several applications.

I read through the documentation of IdentityServer4 but could not find anything related to a proposed structure.

Does anybody have a clear structure setup which could be used to understand the whole setup? (Separation like Asp.Net MVC Boilerplate, IdentityServer4, Protected Api.)

IdentityServer4 Config:

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            ////.AddSigningCredential(...) // Strongly recommended, if you want something more secure than developer signing (Read The Manual since it's highly recommended)
            .AddInMemoryIdentityResources(InMemoryInitConfig.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryInitConfig.GetApiResources())
            .AddInMemoryClients(InMemoryInitConfig.GetClients())
            .AddLdapUsers<OpenLdapAppUser>(Configuration.GetSection("IdentityServerLdap"), UserStore.InMemory);
    }

IdentityServer4 InMemoryInitConfig:

namespace QuickstartIdentityServer{
public class InMemoryInitConfig
{
    // scopes define the resources in your system
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
        };
    }

    // clients want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
        // client credentials client
        return new List<Client>
        {
            
            //DEMO HTTP CLIENT
            new Client
            {
                ClientId = "demo",
                ClientSecrets = new List<Secret> {new Secret("password".Sha256()) } ,
                ClientName = "demo",
                AllowedGrantTypes = {
                    GrantType.ClientCredentials, // Server to server
                    GrantType.ResourceOwnerPassword, // User to server
                    GrantType.Implicit
                },

                //GrantTypes.HybridAndClientCredentials,
                AllowAccessTokensViaBrowser = true,

                AllowOfflineAccess = true,
                AccessTokenLifetime = 90, // 1.5 minutes
                AbsoluteRefreshTokenLifetime = 0,
                RefreshTokenUsage = TokenUsage.OneTimeOnly,
                RefreshTokenExpiration = TokenExpiration.Sliding,
                UpdateAccessTokenClaimsOnRefresh = true,
                RequireConsent = false,

                RedirectUris = {
                    "http://localhost:6234/"
                },

                PostLogoutRedirectUris = { "http://localhost:6234" },
                AllowedCorsOrigins ={ "http://localhost:6234/" },

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                },
                
            },

            

        };
    }
}

}

My client config:

public void Configuration(IAppBuilder app)

    {
        
        app.UseAbp();

        app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);

        // ABP
        //app.UseCookieAuthentication(new CookieAuthenticationOptions
        //{
        //    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        //    LoginPath = new PathString("/Account/Login"),
        //    // evaluate for Persistent cookies (IsPermanent == true). Defaults to 14 days when not set.
        //    //ExpireTimeSpan = new TimeSpan(int.Parse(ConfigurationManager.AppSettings["AuthSession.ExpireTimeInDays.WhenPersistent"] ?? "14"), 0, 0, 0),
        //    //SlidingExpiration = bool.Parse(ConfigurationManager.AppSettings["AuthSession.SlidingExpirationEnabled"] ?? bool.FalseString)
        //    ExpireTimeSpan = TimeSpan.FromHours(12),
        //    SlidingExpiration = true
        //});
        // END ABP

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

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Authority = "http://localhost:5443", //ID Server
            ClientId = "demo",
            ClientSecret = "password",
            ResponseType = "id_token token",
            SignInAsAuthenticationType = "Cookies",
            RedirectUri = "http://localhost:6234/", //URL of website when cancel login on idsvr4
            PostLogoutRedirectUri = "http://localhost:6234", //URL Logout ??? << when this occor
            Scope = "openid",
            RequireHttpsMetadata = false,

            //AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,

        });
        /// END IDENTITYSERVER

        app.UseExternalSignInCookie("Cookies");
        
        app.MapSignalR();
    }

UPDATE

I was reading the documentation on OpenID Connect and saw that it is possible to create notifications for httpContext to take the user's claims in the Idsrv4 userinfo endpoint like this:

public void Configuration(IAppBuilder app)

    {
        
        app.UseAbp();

        // ABP
        //app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);

        //app.UseCookieAuthentication(new CookieAuthenticationOptions
        //{
        //    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        //    LoginPath = new PathString("/Account/Login"),
        //    // evaluate for Persistent cookies (IsPermanent == true). Defaults to 14 days when not set.
        //    //ExpireTimeSpan = new TimeSpan(int.Parse(ConfigurationManager.AppSettings["AuthSession.ExpireTimeInDays.WhenPersistent"] ?? "14"), 0, 0, 0),
        //    //SlidingExpiration = bool.Parse(ConfigurationManager.AppSettings["AuthSession.SlidingExpirationEnabled"] ?? bool.FalseString)
        //    ExpireTimeSpan = TimeSpan.FromHours(12),
        //    SlidingExpiration = true
        //});
        // END ABP

        /// IDENTITYSERVER
        AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject;
        JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear();

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

        // CONFIG OPENID
        var openIdConfig = new OpenIdConnectAuthenticationOptions
        {
            Authority = "http://localhost:5443", //ID Server
            ClientId = "demo",
            ClientSecret = "password",
            ResponseType = "id_token token",
            SignInAsAuthenticationType = "Cookies",
            RedirectUri = "http://localhost:6234/", //URL of website when cancel login on idsvr4
            PostLogoutRedirectUri = "http://localhost:6234", //URL Logout ??? << when this occor
            Scope = "openid profile api1",
            RequireHttpsMetadata = false,
            
            // get userinfo
            Notifications = new OpenIdConnectAuthenticationNotifications {
                SecurityTokenValidated = async n => {
                    var userInfoClient = new UserInfoClient(
                        new Uri(n.Options.Authority + "/connect/userinfo"),
                              n.ProtocolMessage.AccessToken);

                    var userInfo = await userInfoClient.GetAsync();
                    
                    // create new identity and set name and role claim type
                    var nid = new ClaimsIdentity(
                        n.AuthenticationTicket.Identity.AuthenticationType,
                        ClaimTypes.GivenName,
                        ClaimTypes.Role);

                    foreach (var x in userInfo.Claims) {
                        nid.AddClaim(new Claim(x.Item1, x.Item2));        
                    }

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

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

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

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

                    n.AuthenticationTicket = new AuthenticationTicket(
                        nid,
                        n.AuthenticationTicket.Properties);

                    n.Request.Headers.SetValues("Authorization ", new string[] { "Bearer ", n.ProtocolMessage.AccessToken });

                }
            }

        };
        // END CONFIG OPENID

        app.UseOpenIdConnectAuthentication(openIdConfig);
        
        /// END IDENTITYSERVER

        app.UseExternalSignInCookie("Cookies");
        
        app.MapSignalR();
    }

UPDATE 2

Thank you @Khanh TO,

I did exactly what you recommended, I kept the database of each application

However to no longer manage the users by the application database, I hardcode a method that takes from the idsr4 userinfo endpoint

The information needed to create or update a user in the abpUsers table, then the application interprets the data and does the necessary actions

More specifically: In the redirect_uri I send to the AccountController of my client, there I have an ActionResult that does all this work calling the necessary methods to create/update an user on client userstable

like image 426
Gunblades Avatar asked Apr 19 '19 12:04

Gunblades


People also ask

How to integrate identityserver with ASP NET Core Identity?

Notice the reference to IdentityServer4.AspNetIdentity . This NuGet package contains the ASP.NET Core Identity integration components for IdentityServer. In ConfigureServices notice the necessary AddDbContext<ApplicationDbContext> and AddIdentity<ApplicationUser, IdentityRole> calls are done to configure ASP.NET Core Identity.

How do I Secure Web APIs and spas with identityserver4?

ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following: IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. IdentityServer4 enables the following security features: For more information, see Welcome to IdentityServer4.

Where can I find the identityserver4 source code?

You can see the following steps in the IdentityServer4 docs for more details. The last output will be a visual studio 2019 solution that contains: This is the project that is responsible for securing APIs, configuring your clients and storing users’ data. You can see the source code on Github.

Why do we need identityserver4?

So ASP.NET Identity is Authentication and Authorization provider. Why do we need IdentityServer4? ASP.NET Identity can receive a security token from a third-party login provider like Facebook, Google, Microsoft and Twitter.


1 Answers

I think the GrantType.ResourceOwnerPassword flow doens't support AD login and not support by the UseOpenIdConnectAuthentication neither , you can use Implicit or Hybrid flow.
Once you authenticate the to your client mvc app, you can view any claims in HttpContext.User and find the correct claim value as user's identity (they are just claims , and no need to create a local account)

like image 121
John Avatar answered Oct 30 '22 13:10

John