12

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

Community
  • 1
  • 1
Mirusky
  • 372
  • 3
  • 16
  • Ok read this, https://www.codeproject.com/Articles/1263539/OWIN-OAuth2-Authentication-for-Facebook-and-Google there is three diffrent auth here inc CookieAuthenticationOptions – Alen.Toma Apr 22 '19 at 20:14
  • so I dont want to create the users inside my client (web app that authenticates with the identityserver), I want the users to be all just on the side of the Identityserver – Mirusky Apr 22 '19 at 20:22
  • Did you read about OpenId and OAuth? I would start reading the quick starts from IdentityServer.. this question is too broad for us to answer here. There are simply too many moving parts involved. – jpgrassi Apr 23 '19 at 19:55
  • Did you take a look at the [sample code](https://github.com/IdentityServer/IdentityServer4.Samples)? –  Apr 26 '19 at 06:54
  • I saw but all clients are based on .NET CORE but i'm using a app based on .net Framework with oidc client – Mirusky Apr 26 '19 at 21:46
  • 2
    @Gunblades I'm not sure i understand the question completely. But i suggest to store users in each application as well with `specific` information for each application (but no authentication information). The structure is like `Users(Id, IdentityServerUserId,.....more specific information)`, the `IdentityServerUserId` is the `sub claim` from your identityserver, you could also have a Roles table in each application that stores `specific roles`. Then you can use the `IdentityServerUserId` to map to each user after the user logs in with your identity server. – Khanh TO Apr 27 '19 at 11:18
  • 1
    The point is each application has different information need, we centralize all the information including authentication information in your identity server, but we extend it with more specific information for each application. – Khanh TO Apr 27 '19 at 11:21
  • 1
    I read the post with **update** and still got now clear feeling whether you solved your problem and if not, what the problem is, @Gunblades – d_f Apr 30 '19 at 09:57
  • @KhanhTO Write an answer? – aaron Apr 30 '19 at 23:25
  • @aaron: I'm not sure if i really solved the problem in the question – Khanh TO May 05 '19 at 02:53
  • 1
    @Gunblades Is the problem in the question solved? – aaron May 05 '19 at 03:07
  • Yash it is solved – Mirusky May 13 '19 at 15:01

1 Answers1

5

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)

John
  • 716
  • 1
  • 7
  • 24
  • I'm using the extension(https://github.com/Nordes/IdentityServer4.LdapExtension) that allows me to use `GrantType.ResourceOwnerPassword` to do this – Mirusky Apr 26 '19 at 16:00
  • 1
    @Gunblades Oh, I havn't try it yet, but I think the `UseOpenIdConnectAuthentication` itself dosn't support password flow too. password flow require your app to receive the username and password and pass it to your authentication center api ( that mean you must have login page in your app) . just try `implict` or `hybired` flow , it's very easy to use ( those flow will redirect your app to authentication center's login page and redirect back when u successfully logined) – John Apr 27 '19 at 03:46