Dangl.Identity Client Libraries
Dangl.Identity is an OpenID / OAuth2 capable server that offers single sign on functionalities.
It's primary is available at https://identity.dangl-it.com with a fallback at https://identity.dangl-it.de.
It works with all OpenID Connect compatible clients and it's configuration is available here.
The Dangl.Identity.Client libraries offer specialised classes and utilities that make integrating and connecting with Dangl.Identity easy. For reference, please check the official documentation for IdentityServer, which is used internally by Dangl.Identity.
Default Configuration
By default, the identity servers are expected to be reachable at https://identity.dangl-it.com
and https://identity.dangl-it.de
. Fallback happens automatically to the latter one if the former is unreachable.
Dangl.Identity.Client
This project includes the DanglIdentityLoginHandler
that offers JWT / OAuth2 login and refresh functionalities for clients that have the ResourceOwnerPasswordGrant
enabled.
The DanglIdentityClientCredentialsLoginHandler
can be used with clients that have the ClientCredentials
grant enabled, for example in server-side applications.
Dangl.Identity.Client.Mvc
This project includes utilities for integrating Dangl.Identity in Asp.Net Core web applications. This is useful when a web application wants to delegate it's user and identity management to Dangl.Identity.
User Information Transmission
When you're using Dangl.Identity.Client.Mvc, it's possible that you're calling other services that authenticate
via Dangl.Identity. Typically, this is done via the ClientCredentials
claim, meaning your backend authenticates
as client without a user context.
In some cases, however, you want to transmit user ids along with the request to indicate who initiated the action. For example,
Dangl.AVACloud conversions may be called by the Dangl.WebGAEB service. In such cases, Dangl.WebGAEB may say "I'm doing the
conversion for user Bob
". This can be done between trusted clients when using Dangl.Identity authentication.
For the sender, meaning the one who initiates the request, it's necessary to fulfil two requirements:
- The necessary permission must be given to the client. This means the client must be configured in Dangl.Identity
with the client claim
user_delegation_allowed
set totrue
. The claim name is defined inDangl.Identity.Client.Mvc.Configuration.AuthenticationConstants.CLIENT_CLAIM_USER_ID_TRANSMISSION
and must be manually configured in Dangl.Identity for each client. - The client must attach the current users id in a Http header called
X-DanglIdentityUserId
. The header name is defined inDangl.Identity.Client.Mvc.Configuration.AuthenticationConstants.USER_ID_TRANSMISSION_HEADER
. There are also headers for user emails, usernames and identicon ids.
To make this easier for the sender, the Dangl.Identity.Client.Mvc.Services.UserInformationTransmissionHttpHandler
can
be used to automatically add the user id header to outgoing Http requests to whitelisted domains. The class can either instantiated
transient with the constructor that accepts an IUserInfoService
, or it can be created with an IHttpContextAccessor
to resolve
its own dependencies for each operation. The second overload is useful when used in combination with the HttpClientFactory
, where
instances of this handler may be kept alive and used in multiple, different requests.
The context accessor should be provided via services.AddHttpContextAccessor()
.
For the receiver, there is nothing special to do. The default implementation of the IUserInfoService
will treat
such transmitted user ids the same as regular authenticated users.
Please note that no other user information is transferred in such requests, neither claims or roles are transferred.
Dangl.Identity.Client.App
This is a generated client to be used in apps to connect with web services that use Dangl.Identity. The code is generated via nSwag.
Asp.Net Core Identity Server Side Integration
Add the following to the ConfigureServices
method in your Startup
:
services.AddDbContext<IntegrationTestsContext>(o => o.UseSqlite(sqliteConnectionString));
var danglIdentityServerConfig = new DanglIdentityServerConfiguration()
.SetClientId(_clientId)
.SetClientSecret(_clientSecret)
.SetRequiredScope(_requiredScopes)
.SetBaseUri(_baseUri)
.SetFallbackBaseUri(_fallbackBaseUri)
.SetUseDanglIdentityJwtAuthentication(true); // Use this last call if you're intending to use the
// app.UseDanglIdentityJwtTokenAuthentication() middleware instead of relying on automatic
// authentication per requests. This prevents failed authentication tries when using multiple Jwt
// authorities, e.g. base and fallback. If this is not set to true, a default AuthorizationPolicy
// will always try to authenticate against all known login schemes.
// This will also disable redirects to login for original 401 and 403 responses
// Cookie based login will also be deactivated in the DanglIdentityController
services.AddControllersWithDanglIdentity<IntegrationTestsContext, IntegrationTestsUser, IntegrationTestsRole>(danglIdentityServerConfig);
With clientId
, clientSecret
and requiredScopes
being the respective values as registered
with Dangl.Identity
.
There are more optional parameters present. Use relativeLoginUrl
to communicate the apps specific login path.
This is used to generate correct links for users when they confirm their emails or reset passwords.
By default, the option UseMemoryCacheUserInfoUpdater
is set to true
. This means that the configuration automatically
adds the default Asp.Net Core IMemoryCache
implementation and uses it in the default MemoryCacheUserInfoUpdaterCache : IUserInfoUpdaterCache
.
This is required, because Jwt tokens are checked for user profile data which is then persisted to the local database, e.g. for creating UserId references
in foreign keys. To not have to do database roundtrips, each single Jwt token is only checked once, then only again after it has expired and was refreshed.
Additionally, if Jwt tokens are used for authentication and you want to synchronize user data to a local database, add app.UseDanglIdentityJwtTokenUserInfoUpdater()
after UseAuthentication
, like in the following example:
app.UseAuthentication();
// Use this instead of AddAuthentication() if you're using Jwt tokens
// app.UseDanglIdentityJwtTokenAuthentication(danglIdentityBaseUri, danglIdentityFallbackUri);
app.UseDanglIdentityJwtTokenUserInfoUpdater();
app.UseMvc();
The DanglIdentityUserInfoUpdater
will evaluate requests that contain a Jwt token and create or update the user data in the database. Tokens are cached in the
provided IUserInfoUpdaterCache
, meaning there should only be a single database hit for every token. With a default expiration time of one hour, most requests
will not hit the database while still providing relatively up-to-date user data. If the token changes, the database will be hit again.
Additionally, the DanglIdentityUserInfoUpdater
is called when user information is read from client headers in a service-to-service
communication scenario.
Restricted Jwt Access
Optionally, you can use the properties RequiredJwtRoles
and RequiredJwtClaims
on DanglIdentityServerConfiguration
to restrict logins with Jwt tokens.
IUserInfoService
Dangl.Identity.Client.Mvc provides an IUserInfoService
:
namespace Dangl.Identity.Client.Mvc.Services
{
public interface IUserInfoService
{
Task<bool> UserIsAuthenticatedAsync();
Task<bool> ClientIsAuthenticatedAsync();
Task<Guid> GetCurrentUserIdAsync();
Task<Guid> GetCurrentUserIdenticonIdAsync();
Task<Guid> GetCurrentClientIdAsync();
Task<string> GetUserIpAddressAsync();
Task<List<Claim>> GetUserClaimsAsync();
Task<List<Claim>> GetClientClaimsAsync();
}
}
Notes:
UserIsAuthenticatedAsync
returns onlytrue
if an actual user is authenticated, it isfalse
for client credential grantsGetUserClaimsAsync
returns all claimsGetClientClaimsAsync
returns only claims whosetype
starts withclient_
Integration with Dangl.Identity.TestHost
When you have multiple TestHost
instances, there might be problems with AuthorizationPolicy
.
The Dangl.Identity server / test host defines a policy that is somehow shared across
all TestServer
instances.
Add this to your Setup
for the integration tests:
var danglIdentityServerConfig = new DanglIdentityServerConfiguration()
.SetAuthorizationSetupAction(o =>
{
o.AddPolicy("scope01", builder => builder.RequireScope("integration_tests"));
o.AddPolicy("scope02", builder => builder.RequireScope("different_scope"));
o.AddPolicy("DelegatedAccountAccess", builder => builder.RequireAssertion(c => { return true; }));
o.AddPolicy("AdminOrAuthenticationConnector", builder => builder.RequireAssertion(c => { return true; }));
o.AddPolicy("IconManagerPolicy", builder => builder.RequireAssertion(c => { return true; }));
});
TestHost Configuration
When working with the Dangl.Identity.TestHost and Jwt Bearer Authentication, e.g. with Dangl.Identity.Client.Mvc, the base
uri for Dangl.Identity should be set to the test hosts uri value of https://dangl-identity-testhost
so that Jwt
token validation does work as expected. This value is also available in the TestHost package under DanglIdentityTestServerManager.DANGL_IDENTITY_TESTHOST_BASE_ADDRESS
.
Login Method in Your Controllers
Use dependency injection to get a service of type IDanglIdentitySignInManager
to perform
a cookie-backed sign in attemtp via identifier (username or email) and password.
Included DanglIdentityController
There are custom endpoints for the following actions available. Please note that Cookie based authentication is disabled if Jwt is chosen and vice versa.
Login with Cookie
POST /identity/login
Body:
{
"identifier": <username or email>,
"password": <password",
"staySignedIn": <boolean>
}
This accepts an optional redirectUrl
query parameter, e.g.
POST /identity/login?redirectUrl=home
Logout with Cookie
DELETE /identity/login
Login and Return Jwt Bearer Token
POST /identity/token-login
Body:
{
"identifier": <username or email>,
"password": <password"
}
Response
{
"accessToken": string
"identityToken": string
"tokenType": string
"refreshToken": string
"errorDescription": string
"expiresIn": number
}
Refresh Jwt Bearer Token
POST /identity/token-refresh
Body:
{
"refreshToken": <refresh token>
}
Response
{
"accessToken": string
"identityToken": string
"tokenType": string
"refreshToken": string
"errorDescription": string
"expiresIn": number
}
Disable Claims Transformation for Jwt
Add the following to startup. You can also call Clear()
to remove all mappings.
// This disables that Jwt tokens are mapped to Microsoft specific
// ones, so it preserves the original claim names.
// idp = identity provider
// sub = subject = user id
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("idp");
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
Demo App
The Dangl.Identity.DemoClient is a sample Asp.Net Core MVC application that integrates with Dangl.Identity.
Please note that there should be a call to app.UseAuthentication()
in the Configure()
method before using MVC.
The Dangl.Identity.ApiDemoClient app is only used as an example to show how the integrated Jwt token controller can be used. The Swagger definition for the generated TypeScript client is used from it.
Using OpenID
In Startup.cs
, add the following to ConfigureServices()
:
var danglIdentityConfig = new DanglIdentityConfig(clientId, clientSecret, baseUri: danglIdentityAuthority);
services.AddDanglIdentityOpenId(danglIdentityConfig);
services.AddMvc();
If you are not authenticated, you will be redirected to Dangl.Identity to perform the login and grant consent.
When using OpenID, the following identity resources / scopes must be allowed for the client:
email
openid
profile
roles
Additionally, clients should be configured to always send user claims in the id token, since that is where the client
will extract user information from.
Additionally, you can provide an implementation (or the default) IUserInfoUpdater
to sync user data on login to a local store.
If you are using Cookie based authentication, it is important to add AntiForgery validation to the MvcOptions:
mvcOptions.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
Using Jwt Authentication
For API projects, you will most likely use Jwt authentication instead of application cookies. Add the following in Startup.cs
to ConfigureServices()
:
services.AddDbContext<IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>>(o => o.UseInMemoryDatabase(Guid.NewGuid().ToString()));
var serverConfig = new OAuth.Configuration.DanglIdentityServerConfiguration()
.SetBaseUri(danglIdentityAuthority)
.SetClientId(clientId)
.SetClientSecret(clientSecret)
.SetRequiredScope("openid")
.SetUseDanglIdentityOpenIdCookieAuthentication(true);
services.AddControllersWithDanglIdentity<IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>, IdentityUser<Guid>, IdentityRole<Guid>>(serverConfig);
Swagger Generation
The Dangl.Identity.Client.App packages contains an automatically generated .NET client to connect with MVC web apps that use the Dangl.Identity delegating features.
In case you are using Swagger to generate your applications clients AND you also include the DanglIdentityController
from the Dangl.Identity.Client.Mvc packages, please note the following:
The System.Net.HttpStatusCode
enum is used in some response objects. This enum defines multiple values with the same integer value. This might cause runtime failures with generated code.
To circumvent this, you should exclude the HttpStatusCode
type in the generated C# client and add an additional namespace usage of System.Net
. This will ensure that you generated client
just uses the default System.Net.HttpStatusCode
enum without relying on the generated one.
Angular Client
The public README contains a description of the Angular client.
Assembly Strong Naming & Usage in Signed Applications
This module produces strong named assemblies when compiled. When consumers of this package require strongly named assemblies, for example when they
themselves are signed, the outputs should work as-is.
The key file to create the strong name is adjacent to the csproj
file in the root of the source project. Please note that this does not increase
security or provide tamper-proof binaries, as the key is available in the source code per
Microsoft guidelines