diff --git a/src/KeycloakIdentityModel/KeycloakIdentity.cs b/src/KeycloakIdentityModel/KeycloakIdentity.cs
index 1fd9e67..acd8f7d 100644
--- a/src/KeycloakIdentityModel/KeycloakIdentity.cs
+++ b/src/KeycloakIdentityModel/KeycloakIdentity.cs
@@ -14,6 +14,7 @@
using KeycloakIdentityModel.Models.Responses;
using KeycloakIdentityModel.Utilities;
using KeycloakIdentityModel.Utilities.ClaimMapping;
+using Microsoft.Owin;
using Newtonsoft.Json.Linq;
namespace KeycloakIdentityModel
@@ -124,34 +125,35 @@ public override bool TryRemoveClaim(Claim claim)
///
public override ClaimsIdentity Clone()
{
- return Task.Run(ToClaimsIdentityAsync).Result;
+ throw new NotImplementedException();
+ //return Task.Run(() => ToClaimsIdentityAsync(context)).Result;
}
///
/// Refreshes and re-authenticates the current identity from the Keycloak instance (only if necessary)
///
///
- public Task RefreshIdentityAsync()
+ public Task RefreshIdentityAsync(IOwinContext context)
{
- return GetClaimsAsync();
+ return GetClaimsAsync(context);
}
///
/// Refreshes and returns the updated claims for the identity (refreshes only if necessary)
///
///
- public Task> GetUpdatedClaimsAsync()
+ public Task> GetUpdatedClaimsAsync(IOwinContext context)
{
- return GetClaimsAsync();
+ return GetClaimsAsync(context);
}
///
/// Returns a static base representation of the identity as a claims identity
///
///
- public async Task ToClaimsIdentityAsync()
+ public async Task ToClaimsIdentityAsync(IOwinContext context)
{
- return new ClaimsIdentity(await GetClaimsAsync(), AuthenticationType);
+ return new ClaimsIdentity(await GetClaimsAsync(context), AuthenticationType);
}
#endregion
@@ -164,12 +166,12 @@ public async Task ToClaimsIdentityAsync()
///
///
///
- public static Task ConvertFromClaimsIdentityAsync(IKeycloakParameters parameters,
+ public static Task ConvertFromClaimsIdentityAsync(IOwinContext context, IKeycloakParameters parameters,
ClaimsIdentity identity)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (identity == null) throw new ArgumentNullException(nameof(identity));
- return ConvertFromClaimsAsync(parameters, identity.Claims);
+ return ConvertFromClaimsAsync(context, parameters, identity.Claims);
}
///
@@ -178,7 +180,7 @@ public static Task ConvertFromClaimsIdentityAsync(IKeycloakPar
///
///
///
- public static Task ConvertFromClaimsAsync(IKeycloakParameters parameters,
+ public static Task ConvertFromClaimsAsync(IOwinContext context, IKeycloakParameters parameters,
IEnumerable claims)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
@@ -189,7 +191,7 @@ public static Task ConvertFromClaimsAsync(IKeycloakParameters
var accessToken = claimLookup[Constants.ClaimTypes.AccessToken].FirstOrDefault();
var refreshToken = claimLookup[Constants.ClaimTypes.RefreshToken].FirstOrDefault();
- return ConvertFromJwtAsync(parameters, accessToken, refreshToken, idToken);
+ return ConvertFromJwtAsync(context, parameters, accessToken, refreshToken, idToken);
}
///
@@ -198,12 +200,12 @@ public static Task ConvertFromClaimsAsync(IKeycloakParameters
///
///
///
- public static Task ConvertFromTokenResponseAsync(IKeycloakParameters parameters,
+ public static Task ConvertFromTokenResponseAsync(IOwinContext context, IKeycloakParameters parameters,
TokenResponse message)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (message == null) throw new ArgumentNullException(nameof(message));
- return ConvertFromJwtAsync(parameters, message.AccessToken, message.RefreshToken, message.IdToken);
+ return ConvertFromJwtAsync(context, parameters, message.AccessToken, message.RefreshToken, message.IdToken);
}
///
@@ -213,7 +215,7 @@ public static Task ConvertFromTokenResponseAsync(IKeycloakPara
///
///
///
- public static async Task ConvertFromAuthResponseAsync(IKeycloakParameters parameters,
+ public static async Task ConvertFromAuthResponseAsync(IOwinContext context, IKeycloakParameters parameters,
AuthorizationResponse response, Uri baseUri)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
@@ -221,8 +223,8 @@ public static async Task ConvertFromAuthResponseAsync(IKeycloa
if (baseUri == null) throw new ArgumentNullException(nameof(baseUri));
response.ThrowIfError();
- var message = new RequestAccessTokenMessage(baseUri, parameters, response);
- return await ConvertFromTokenResponseAsync(parameters, await message.ExecuteAsync());
+ var message = new RequestAccessTokenMessage(context, baseUri, parameters, response);
+ return await ConvertFromTokenResponseAsync(context, parameters, await message.ExecuteAsync());
}
///
@@ -233,7 +235,7 @@ public static async Task ConvertFromAuthResponseAsync(IKeycloa
///
///
///
- public static async Task ConvertFromJwtAsync(IKeycloakParameters parameters,
+ public static async Task ConvertFromJwtAsync(IOwinContext context, IKeycloakParameters parameters,
string accessToken, string refreshToken = null, string idToken = null)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
@@ -242,12 +244,12 @@ public static async Task ConvertFromJwtAsync(IKeycloakParamete
var kcIdentity = new KeycloakIdentity(parameters);
try
{
- await kcIdentity.CopyFromJwt(accessToken, refreshToken, idToken);
+ await kcIdentity.CopyFromJwt(context, accessToken, refreshToken, idToken);
}
catch (SecurityTokenExpiredException)
{
// Load new identity from token endpoint via refresh token (if possible)
- await kcIdentity.RefreshIdentity(refreshToken);
+ await kcIdentity.RefreshIdentity(context, refreshToken);
}
return kcIdentity;
}
@@ -259,14 +261,14 @@ public static async Task ConvertFromJwtAsync(IKeycloakParamete
///
///
///
- public static async Task GenerateLoginUriAsync(IKeycloakParameters parameters, Uri baseUri,
+ public static async Task GenerateLoginUriAsync(IOwinContext context, IKeycloakParameters parameters, Uri baseUri,
string state = null)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (baseUri == null) throw new ArgumentNullException(nameof(baseUri));
// Generate login URI and data
- var uriManager = await OidcDataManager.GetCachedContextAsync(parameters);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(context, parameters);
var loginParams = uriManager.BuildAuthorizationEndpointContent(baseUri, state ?? Guid.NewGuid().ToString());
var loginUrl = uriManager.GetAuthorizationEndpoint();
@@ -281,12 +283,12 @@ public static async Task GenerateLoginUriAsync(IKeycloakParameters paramete
///
///
///
- public static async Task GenerateLoginCallbackUriAsync(IKeycloakParameters parameters, Uri baseUri)
+ public static async Task GenerateLoginCallbackUriAsync(IOwinContext context, IKeycloakParameters parameters, Uri baseUri)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (baseUri == null) throw new ArgumentNullException(nameof(baseUri));
- return (await OidcDataManager.GetCachedContextAsync(parameters)).GetCallbackUri(baseUri);
+ return (await OidcDataManager.GetCachedContextAsync(context, parameters)).GetCallbackUri(baseUri);
}
///
@@ -296,14 +298,14 @@ public static async Task GenerateLoginCallbackUriAsync(IKeycloakParameters
///
///
///
- public static async Task GenerateLogoutUriAsync(IKeycloakParameters parameters, Uri baseUri,
+ public static async Task GenerateLogoutUriAsync(IOwinContext context, IKeycloakParameters parameters, Uri baseUri,
string redirectUrl = null)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (baseUri == null) throw new ArgumentNullException(nameof(baseUri));
// Generate logout URI and data
- var uriManager = await OidcDataManager.GetCachedContextAsync(parameters);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(context, parameters);
var logoutParams = uriManager.BuildEndSessionEndpointContent(baseUri, null, redirectUrl);
var logoutUrl = uriManager.GetEndSessionEndpoint();
@@ -317,7 +319,7 @@ public static async Task GenerateLogoutUriAsync(IKeycloakParameters paramet
///
///
///
- public static bool TryValidateParameters(IKeycloakParameters parameters)
+ public static bool TryValidateParameters(IOwinContext context, IKeycloakParameters parameters)
{
try
{
@@ -342,7 +344,7 @@ public static void ValidateParameters(IKeycloakParameters parameters)
// Verify required parameters
if (parameters.KeycloakUrl == null)
throw new ArgumentNullException(nameof(parameters.KeycloakUrl));
- if (parameters.Realm == null)
+ if (parameters.Realm == null && parameters.MultiTenantRealmSelector == null)
throw new ArgumentNullException(nameof(parameters.Realm));
// Set default parameters
@@ -362,17 +364,6 @@ public static void ValidateParameters(IKeycloakParameters parameters)
if (parameters.PostLogoutRedirectUrl != null &&
!Uri.IsWellFormedUriString(parameters.PostLogoutRedirectUrl, UriKind.RelativeOrAbsolute))
throw new ArgumentException(nameof(parameters.PostLogoutRedirectUrl));
-
- // Attempt to refresh OIDC metadata from endpoint (on separate thread)
- try
- {
- Task.Run(() => OidcDataManager.GetCachedContextAsync(parameters)).Wait();
- }
- catch (Exception exception)
- {
- throw new ArgumentException("Invalid Keycloak server parameters specified: See inner for server error",
- exception);
- }
}
#endregion
@@ -430,7 +421,7 @@ private IEnumerable GetCurrentClaims()
return _kcClaims.Concat(_userClaims);
}
- private async Task> GetClaimsAsync()
+ private async Task> GetClaimsAsync(IOwinContext context)
{
await _refreshLock.WaitAsync();
try
@@ -443,7 +434,7 @@ private async Task> GetClaimsAsync()
throw new Exception("Both the access token and the refresh token have expired");
// Load new identity from token endpoint via refresh token
- await RefreshIdentity(_refreshToken.RawData);
+ await RefreshIdentity(context, _refreshToken.RawData);
}
return GetCurrentClaims();
@@ -454,13 +445,13 @@ private async Task> GetClaimsAsync()
}
}
- protected async Task CopyFromJwt(string accessToken, string refreshToken = null, string idToken = null)
+ protected async Task CopyFromJwt(IOwinContext context, string accessToken, string refreshToken = null, string idToken = null)
{
if (accessToken == null) throw new ArgumentException(nameof(accessToken));
// Validate JWTs provided
var tokenHandler = new KeycloakTokenHandler();
- var uriManager = await OidcDataManager.GetCachedContextAsync(_parameters);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(context, _parameters);
SecurityToken accessSecurityToken, idSecurityToken = null, refreshSecurityToken = null;
@@ -491,11 +482,11 @@ protected async Task CopyFromJwt(string accessToken, string refreshToken = null,
_refreshToken = refreshSecurityToken as JwtSecurityToken;
}
- protected async Task RefreshIdentity(string refreshToken)
+ protected async Task RefreshIdentity(IOwinContext context, string refreshToken)
{
var respMessage =
- await new RefreshAccessTokenMessage(_parameters, refreshToken).ExecuteAsync();
- await CopyFromJwt(respMessage.AccessToken, respMessage.RefreshToken, respMessage.IdToken);
+ await new RefreshAccessTokenMessage(context, _parameters, refreshToken).ExecuteAsync();
+ await CopyFromJwt(context, respMessage.AccessToken, respMessage.RefreshToken, respMessage.IdToken);
IsTouched = true;
}
diff --git a/src/KeycloakIdentityModel/KeycloakIdentityModel.csproj b/src/KeycloakIdentityModel/KeycloakIdentityModel.csproj
index 6e81d1f..64b27fb 100644
--- a/src/KeycloakIdentityModel/KeycloakIdentityModel.csproj
+++ b/src/KeycloakIdentityModel/KeycloakIdentityModel.csproj
@@ -44,10 +44,22 @@
..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll
True
+
+ ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
+ True
+
+
+ ..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll
+ True
+
..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
True
+
+ ..\packages\Owin.1.0\lib\net40\Owin.dll
+ True
+
diff --git a/src/KeycloakIdentityModel/Models/Configuration/DefaultKeycloakParameters.cs b/src/KeycloakIdentityModel/Models/Configuration/DefaultKeycloakParameters.cs
index 98bed68..d6c2b6c 100644
--- a/src/KeycloakIdentityModel/Models/Configuration/DefaultKeycloakParameters.cs
+++ b/src/KeycloakIdentityModel/Models/Configuration/DefaultKeycloakParameters.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.Owin;
namespace KeycloakIdentityModel.Models.Configuration
{
@@ -23,6 +24,14 @@ public class DefaultKeycloakParameters : IKeycloakParameters
///
public string Realm { get; set; }
+ ///
+ /// OPTIONAL: The MultiTenantRealmSelector is used to dynamically set the Realm for multi-tenant applications.
+ ///
+ ///
+ /// - The purpose is for multi-tenant platforms that require multiple Realms.
+ ///
+ public Func MultiTenantRealmSelector { get; set; }
+
///
/// The client ID to use for the application
///
diff --git a/src/KeycloakIdentityModel/Models/Configuration/IKeycloakSettings.cs b/src/KeycloakIdentityModel/Models/Configuration/IKeycloakSettings.cs
index f1fb71d..5a898e2 100644
--- a/src/KeycloakIdentityModel/Models/Configuration/IKeycloakSettings.cs
+++ b/src/KeycloakIdentityModel/Models/Configuration/IKeycloakSettings.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.Owin;
namespace KeycloakIdentityModel.Models.Configuration
{
@@ -7,6 +8,7 @@ public interface IKeycloakParameters
string AuthenticationType { get; }
string KeycloakUrl { get; }
string Realm { get; }
+ Func MultiTenantRealmSelector { get; }
string ClientId { get; }
string ClientSecret { get; }
string Scope { get; }
diff --git a/src/KeycloakIdentityModel/Models/Messages/RefreshAccessTokenMessage.cs b/src/KeycloakIdentityModel/Models/Messages/RefreshAccessTokenMessage.cs
index 244e0af..d2198e5 100644
--- a/src/KeycloakIdentityModel/Models/Messages/RefreshAccessTokenMessage.cs
+++ b/src/KeycloakIdentityModel/Models/Messages/RefreshAccessTokenMessage.cs
@@ -3,18 +3,21 @@
using KeycloakIdentityModel.Models.Configuration;
using KeycloakIdentityModel.Models.Responses;
using KeycloakIdentityModel.Utilities;
+using Microsoft.Owin;
namespace KeycloakIdentityModel.Models.Messages
{
public class RefreshAccessTokenMessage : GenericMessage
{
- public RefreshAccessTokenMessage(IKeycloakParameters options, string refreshToken)
+ public RefreshAccessTokenMessage(IOwinContext context, IKeycloakParameters options, string refreshToken)
: base(options)
{
if (refreshToken == null) throw new ArgumentNullException();
RefreshToken = refreshToken;
+ Context = context;
}
+ private IOwinContext Context { get; }
private string RefreshToken { get; }
public override async Task ExecuteAsync()
@@ -24,7 +27,7 @@ public override async Task ExecuteAsync()
private async Task ExecuteHttpRequestAsync()
{
- var uriManager = await OidcDataManager.GetCachedContextAsync(Options);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(Context, Options);
var response =
await
SendHttpPostRequest(uriManager.GetTokenEndpoint(),
diff --git a/src/KeycloakIdentityModel/Models/Messages/RequestAccessTokenMessage.cs b/src/KeycloakIdentityModel/Models/Messages/RequestAccessTokenMessage.cs
index f5cef22..420f585 100644
--- a/src/KeycloakIdentityModel/Models/Messages/RequestAccessTokenMessage.cs
+++ b/src/KeycloakIdentityModel/Models/Messages/RequestAccessTokenMessage.cs
@@ -3,12 +3,13 @@
using KeycloakIdentityModel.Models.Configuration;
using KeycloakIdentityModel.Models.Responses;
using KeycloakIdentityModel.Utilities;
+using Microsoft.Owin;
namespace KeycloakIdentityModel.Models.Messages
{
public class RequestAccessTokenMessage : GenericMessage
{
- public RequestAccessTokenMessage(Uri baseUri, IKeycloakParameters options,
+ public RequestAccessTokenMessage(IOwinContext context, Uri baseUri, IKeycloakParameters options,
AuthorizationResponse authResponse)
: base(options)
{
@@ -17,10 +18,12 @@ public RequestAccessTokenMessage(Uri baseUri, IKeycloakParameters options,
BaseUri = baseUri;
AuthResponse = authResponse;
+ Context = context;
}
protected Uri BaseUri { get; }
private AuthorizationResponse AuthResponse { get; }
+ private IOwinContext Context { get; }
public override async Task ExecuteAsync()
{
@@ -29,7 +32,7 @@ public override async Task ExecuteAsync()
private async Task ExecuteHttpRequestAsync()
{
- var uriManager = await OidcDataManager.GetCachedContextAsync(Options);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(Context, Options);
var response = await SendHttpPostRequest(uriManager.GetTokenEndpoint(),
uriManager.BuildAccessTokenEndpointContent(BaseUri, AuthResponse.Code));
return await response.Content.ReadAsStringAsync();
diff --git a/src/KeycloakIdentityModel/Utilities/KeycloakTokenHandler.cs b/src/KeycloakIdentityModel/Utilities/KeycloakTokenHandler.cs
index cd83ea2..069c2bd 100644
--- a/src/KeycloakIdentityModel/Utilities/KeycloakTokenHandler.cs
+++ b/src/KeycloakIdentityModel/Utilities/KeycloakTokenHandler.cs
@@ -6,14 +6,15 @@
using System.Threading.Tasks;
using KeycloakIdentityModel.Models.Configuration;
using Microsoft.IdentityModel;
+using Microsoft.Owin;
namespace KeycloakIdentityModel.Utilities
{
internal class KeycloakTokenHandler : JwtSecurityTokenHandler
{
- public static async Task ValidateTokenRemote(string jwt, IKeycloakParameters options)
+ public static async Task ValidateTokenRemote(IOwinContext context, string jwt, IKeycloakParameters options)
{
- var uriManager = await OidcDataManager.GetCachedContextAsync(options);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(context, options);
return await ValidateTokenRemote(jwt, uriManager);
}
@@ -48,9 +49,9 @@ public bool TryValidateToken(string jwt, IKeycloakParameters options, OidcDataMa
}
}
- public async Task ValidateTokenAsync(string jwt, IKeycloakParameters options)
+ public async Task ValidateTokenAsync(IOwinContext context, string jwt, IKeycloakParameters options)
{
- var uriManager = await OidcDataManager.GetCachedContextAsync(options);
+ var uriManager = await OidcDataManager.GetCachedContextAsync(context, options);
return ValidateToken(jwt, options, uriManager);
}
diff --git a/src/KeycloakIdentityModel/Utilities/OidcDataManager.cs b/src/KeycloakIdentityModel/Utilities/OidcDataManager.cs
index 0d3bc3f..d7accf5 100644
--- a/src/KeycloakIdentityModel/Utilities/OidcDataManager.cs
+++ b/src/KeycloakIdentityModel/Utilities/OidcDataManager.cs
@@ -6,6 +6,7 @@
using KeycloakIdentityModel.Models.Configuration;
using KeycloakIdentityModel.Utilities.Synchronization;
using Microsoft.IdentityModel.Protocols;
+using Microsoft.Owin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -21,16 +22,20 @@ public class OidcDataManager
private readonly IKeycloakParameters _options;
private readonly ReaderWriterLockSlim _refreshLock = new ReaderWriterLockSlim();
+ public static Func MultiTenantRealmSelector;
+
// Thread-safe pipeline locks
private bool _cacheRefreshing;
private DateTime _nextCachedRefreshTime;
- protected OidcDataManager(IKeycloakParameters options)
+ protected OidcDataManager(IOwinContext context, IKeycloakParameters options)
{
_options = options;
_nextCachedRefreshTime = DateTime.Now;
- Authority = _options.KeycloakUrl + "/realms/" + _options.Realm;
+ var realm = MultiTenantRealmSelector != null ? MultiTenantRealmSelector(context) : options.Realm;
+
+ Authority = _options.KeycloakUrl + "/realms/" + realm;
MetadataEndpoint = new Uri(Authority + "/" + OpenIdProviderMetadataNames.Discovery);
TokenValidationEndpoint = new Uri(Authority + "/tokens/validate");
}
@@ -55,10 +60,10 @@ private class Metadata
#region Context Caching
- public static Task ValidateCachedContextAsync(IKeycloakParameters options)
+ public static Task ValidateCachedContextAsync(IOwinContext context, IKeycloakParameters options)
{
- var context = GetCachedContext(options.AuthenticationType);
- return context.ValidateCachedContextAsync();
+ return GetCachedContext(context, options.AuthenticationType)
+ .ValidateCachedContextAsync();
}
private async Task ValidateCachedContextAsync()
@@ -81,40 +86,47 @@ private async Task ValidateCachedContextAsync()
}
}
- public static OidcDataManager GetCachedContext(IKeycloakParameters options)
+ public static OidcDataManager GetCachedContext(IOwinContext context, IKeycloakParameters options)
{
- return GetCachedContext(options.AuthenticationType);
+ return GetCachedContext(context, options.AuthenticationType);
}
- public static OidcDataManager GetCachedContext(string authType)
+ public static OidcDataManager GetCachedContext(IOwinContext context, string authType)
{
- var context = GetCachedContextSafe(authType);
- if (context == null)
+ var oidc = GetCachedContextSafe(context, authType);
+ if (oidc == null)
throw new Exception($"Could not find cached OIDC data manager for module '{authType}'");
- return context;
+ return oidc;
}
- public static Task GetCachedContextAsync(IKeycloakParameters options)
+ public static Task GetCachedContextAsync(IOwinContext context, IKeycloakParameters options)
{
- var context = GetCachedContextSafe(options.AuthenticationType);
- return context != null ? Task.FromResult(context) : CreateCachedContext(options);
+ var oidc = GetCachedContextSafe(context, options.AuthenticationType);
+ return oidc != null ? Task.FromResult(oidc) : CreateCachedContext(context, options);
}
- private static OidcDataManager GetCachedContextSafe(string authType)
+ private static OidcDataManager GetCachedContextSafe(IOwinContext context, string authType)
{
OidcDataManager result;
- return OidcManagerCache.TryGetValue(authType + CachedContextPostfix, out result) ? result : null;
+ var realmPrefix = GetRealmPrefix(context);
+ return OidcManagerCache.TryGetValue(realmPrefix + authType + CachedContextPostfix, out result) ? result : null;
}
- public static async Task CreateCachedContext(IKeycloakParameters options,
+ private static async Task CreateCachedContext(IOwinContext context, IKeycloakParameters options,
bool preload = true)
{
- var newContext = new OidcDataManager(options);
- OidcManagerCache[options.AuthenticationType + CachedContextPostfix] = newContext;
+ var newContext = new OidcDataManager(context, options);
+ var realmPrefix = GetRealmPrefix(context);
+ OidcManagerCache[realmPrefix + options.AuthenticationType + CachedContextPostfix] = newContext;
if (preload) await newContext.ValidateCachedContextAsync();
return newContext;
}
+ private static string GetRealmPrefix(IOwinContext context)
+ {
+ return MultiTenantRealmSelector != null ? MultiTenantRealmSelector(context) + "_": "";
+ }
+
#endregion
#region Metadata Handling
@@ -135,7 +147,8 @@ public async Task TryRefreshMetadataAsync()
public async Task RefreshMetadataAsync()
{
// Get Metadata from endpoint
- var dataTask = HttpApiGet(MetadataEndpoint);
+ var metadataEndpoint = MetadataEndpoint;
+ var dataTask = HttpApiGet(metadataEndpoint);
// Try to get the JSON metadata object
JObject json;
@@ -147,7 +160,7 @@ public async Task RefreshMetadataAsync()
{
// Fail on invalid JSON
throw new Exception(
- $"RefreshMetadataAsync: Metadata address returned invalid JSON object ('{MetadataEndpoint}')",
+ $"RefreshMetadataAsync: Metadata address returned invalid JSON object ('{metadataEndpoint}')",
exception);
}
@@ -187,7 +200,7 @@ public async Task RefreshMetadataAsync()
{
// Fail on invalid URI or metadata
throw new Exception(
- $"RefreshMetadataAsync: Metadata address returned incomplete data ('{MetadataEndpoint}')", exception);
+ $"RefreshMetadataAsync: Metadata address returned incomplete data ('{metadataEndpoint}')", exception);
}
}
diff --git a/src/KeycloakIdentityModel/packages.config b/src/KeycloakIdentityModel/packages.config
index 1d46e63..c542e4c 100644
--- a/src/KeycloakIdentityModel/packages.config
+++ b/src/KeycloakIdentityModel/packages.config
@@ -1,6 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/Owin.Security.Keycloak/Configuration/KeycloakAuthenticationOptions.cs b/src/Owin.Security.Keycloak/Configuration/KeycloakAuthenticationOptions.cs
index f691c11..afa127c 100644
--- a/src/Owin.Security.Keycloak/Configuration/KeycloakAuthenticationOptions.cs
+++ b/src/Owin.Security.Keycloak/Configuration/KeycloakAuthenticationOptions.cs
@@ -1,5 +1,6 @@
using System;
using KeycloakIdentityModel.Models.Configuration;
+using Microsoft.Owin;
using Microsoft.Owin.Security;
namespace Owin.Security.Keycloak
@@ -27,6 +28,14 @@ public KeycloakAuthenticationOptions()
///
public string Realm { get; set; }
+ ///
+ /// OPTIONAL: The MultiTenantRealmSelector is used to dynamically set the Realm for multi-tenant applications.
+ ///
+ ///
+ /// - The purpose is for multi-tenant platforms that require multiple Realms.
+ ///
+ public Func MultiTenantRealmSelector { get; set; }
+
///
/// The client ID to use for the application
///
diff --git a/src/Owin.Security.Keycloak/Extensions/AppBuilderExtension.cs b/src/Owin.Security.Keycloak/Extensions/AppBuilderExtension.cs
index c614a7c..1d6db85 100644
--- a/src/Owin.Security.Keycloak/Extensions/AppBuilderExtension.cs
+++ b/src/Owin.Security.Keycloak/Extensions/AppBuilderExtension.cs
@@ -1,4 +1,5 @@
-using Owin.Security.Keycloak.Middleware;
+using KeycloakIdentityModel.Utilities;
+using Owin.Security.Keycloak.Middleware;
namespace Owin.Security.Keycloak
{
@@ -6,6 +7,7 @@ public static class AppBuilderExtension
{
public static IAppBuilder UseKeycloakAuthentication(this IAppBuilder app, KeycloakAuthenticationOptions options)
{
+ OidcDataManager.MultiTenantRealmSelector = options.MultiTenantRealmSelector;
app.Use(typeof (KeycloakAuthenticationMiddleware), app, options);
return app;
}
diff --git a/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationHandler.cs b/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationHandler.cs
index 9e24efb..7e9afe6 100644
--- a/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationHandler.cs
+++ b/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationHandler.cs
@@ -31,8 +31,8 @@ protected override async Task AuthenticateCoreAsync()
try
{
var authResponse = new TokenResponse(bearerAuthArr[1], null, null);
- var kcIdentity = await KeycloakIdentity.ConvertFromTokenResponseAsync(Options, authResponse);
- var identity = await kcIdentity.ToClaimsIdentityAsync();
+ var kcIdentity = await KeycloakIdentity.ConvertFromTokenResponseAsync(Context, Options, authResponse);
+ var identity = await kcIdentity.ToClaimsIdentityAsync(Context);
SignInAsAuthentication(identity, null, Options.SignInAsAuthenticationType);
return new AuthenticationTicket(identity, new AuthenticationProperties());
}
@@ -57,7 +57,7 @@ public override async Task InvokeAsync()
await ValidateSignInAsIdentities();
// Check for valid callback URI
- var callbackUri = await KeycloakIdentity.GenerateLoginCallbackUriAsync(Options, Request.Uri);
+ var callbackUri = await KeycloakIdentity.GenerateLoginCallbackUriAsync(Context, Options, Request.Uri);
if (!Options.ForceBearerTokenAuth && Request.Uri.GetLeftPart(UriPartial.Path) == callbackUri.ToString())
{
// Create authorization result from query
@@ -77,8 +77,8 @@ stateData[Constants.CacheTypes.AuthenticationProperties] as AuthenticationProper
// Process response
var kcIdentity =
- await KeycloakIdentity.ConvertFromAuthResponseAsync(Options, authResult, Request.Uri);
- var identity = await kcIdentity.ToClaimsIdentityAsync();
+ await KeycloakIdentity.ConvertFromAuthResponseAsync(Context, Options, authResult, Request.Uri);
+ var identity = await kcIdentity.ToClaimsIdentityAsync(Context);
Context.Authentication.User.AddIdentity(identity);
SignInAsAuthentication(identity, properties, Options.SignInAsAuthenticationType);
@@ -185,11 +185,11 @@ private async Task ValidateSignInAsIdentities()
{
if (!origIdentity.HasClaim(Constants.ClaimTypes.AuthenticationType, Options.AuthenticationType))
continue;
- var kcIdentity = await KeycloakIdentity.ConvertFromClaimsIdentityAsync(Options, origIdentity);
+ var kcIdentity = await KeycloakIdentity.ConvertFromClaimsIdentityAsync(Context, Options, origIdentity);
if (!kcIdentity.IsTouched) continue;
// Replace identity if expired
- var identity = await kcIdentity.ToClaimsIdentityAsync();
+ var identity = await kcIdentity.ToClaimsIdentityAsync(Context);
Context.Authentication.User = new ClaimsPrincipal(identity);
SignInAsAuthentication(identity, null, Options.SignInAsAuthenticationType);
}
@@ -247,7 +247,7 @@ private async Task LoginRedirectAsync(AuthenticationProperties properties)
var state = Global.StateCache.CreateState(stateData);
// Redirect response to login
- Response.Redirect((await KeycloakIdentity.GenerateLoginUriAsync(Options, Request.Uri, state)).ToString());
+ Response.Redirect((await KeycloakIdentity.GenerateLoginUriAsync(Context, Options, Request.Uri, state)).ToString());
}
private async Task LogoutRedirectAsync()
@@ -255,7 +255,7 @@ private async Task LogoutRedirectAsync()
// Redirect response to logout
Response.Redirect(
(await
- KeycloakIdentity.GenerateLogoutUriAsync(Options, Request.Uri))
+ KeycloakIdentity.GenerateLogoutUriAsync(Context, Options, Request.Uri))
.ToString());
}
diff --git a/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationMiddleware.cs b/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationMiddleware.cs
index 2e8f4b1..5fa3aa6 100644
--- a/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationMiddleware.cs
+++ b/src/Owin.Security.Keycloak/Middleware/KeycloakAuthenticationMiddleware.cs
@@ -36,7 +36,7 @@ private void ValidateOptions()
// Verify required options
if (Options.KeycloakUrl == null)
ThrowOptionNotFound(nameof(Options.KeycloakUrl));
- if (Options.Realm == null)
+ if (Options.Realm == null && Options.MultiTenantRealmSelector == null)
ThrowOptionNotFound(nameof(Options.Realm));
// Load web root path from config