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