420.7 Keycloak and Abp MVC or Abp Angular - chempkovsky/CS82ANGULAR GitHub Wiki
- KeyCloak can be used as authentication and authorisation server
- Once an application is registered with KeyCloak, you can define one or more roles for that application using the KeyCloak console.
- Once you have created a user in KeyCloak, you can assign one or more registered application roles to that user.
- After login into MVC app you can get all the roles assigned to the current user. KeyCloak sends the roles only for the current app. (In case you registered more than one apps and assigned the roles of other apps to the current user).
- you don't have to rely on scopes
- In short, KeyCloak made with love.
- KeyCloak can be used as authentication only server
- In this case, KeyCloak will be used to authenticate the user and Microsoft Identity middleware to authorize the user.
- In this article we show how to configure Abp MVC (Abp Angular) app to use KeyCloak as authentication only server
- Only login and logout will be discussed
- we just follow the recommended approach
- it is the same
- it is the same
- it is the same
- it is the same
- We do not use
hosts-file as yti was described in the article-
applicationUrlwill be untouched. Too many changes in Abp-app code will be required, since OpenIdDict server will need to be reconfigured. - instead we will use
localhost:portas is
-
- start cmd and run the command:
abp new rupbes.AbpMvc01 --template app --ui-framework mvc --theme leptonx-lite --create-solution-folder --no-tests
- open
AbpMvc01WebModule.cs-file of therupbes.AbpMvc01.Web-project and modifyprivate void ConfigureAuthentication(ServiceConfigurationContext context)as follows:
Click to show the code
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true;
});
context.Services.AddAuthentication()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, "SSO with Keycloak", o =>
{
o.SignInScheme = IdentityConstants.ExternalScheme;
o.SignOutScheme = IdentityConstants.ApplicationScheme;
o.ClientId = "Mvc01";
o.ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // copy Secret from keycloak console
o.Authority = "https://kc.rupbes.by:8445/realms/rupbes.test";
// default value == "/signin-oidc"
// Keycloak "Valid redirect URIs"-param must be set to "/signin-oidc"
o.CallbackPath = new PathString("/signin-oidc");
// Requests received on this path will cause the handler to invoke SignOut using the SignOutScheme.
//o.RemoteSignOutPath = new PathString("/Home/DoLogout"); // default value
// default value == "/signout-callback-oidc"
// Keycloak "Valid post logout redirect URIs"-param must be set to "/signout-callback-oidc"
// o.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
/// <summary>
/// The uri where the user agent will be redirected to after application is signed out from the identity provider.
/// The redirect will happen after the SignedOutCallbackPath is invoked.
/// </summary>
/// <remarks>This URI can be out of the application's domain. By default it points to the root.</remarks>
// o.SignedOutRedirectUri = new PathString("/Home/DoCleanUpCookie"); // default value
o.ResponseType = OpenIdConnectResponseType.Code;
o.UsePkce = true;
// o.Scope.Add("user.read");
// o.MapInboundClaims = false;
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("email");
o.Scope.Add("phone");
// o.Scope.Add("offline_access");
o.SaveTokens = true;
});
}-
open the
launchSettings.jsonfile and copy the value ofapplicationUrlwithout the last backslash. In our case, we have"applicationUrl": "https://localhost:44366/", so we copyhttps://localhost:44366. -
in the keycloak console insert copied value
- Access settings
- Root URL:
https://localhost:44366 - Home URL:
https://localhost:44366
- Root URL:
- Access settings
-
Start
rupbes.AbpMvc01.Web-app and clocklogin.- On the login page click
SSO with Keycloak-button and login into Keycloak astestuser- You will be back on
Loginpage of therupbes.AbpMvc01.Web-app- click register
- You will be back on
- On the login page click
-
logout from the app
- try to login with
SSO with Keycloak-button click- second time it will not ask for password.
- try to login with
-
to remind the user about complete logout we have to override Abp logout page
-
for
rupbes.Abp2Keycloak.Webproject create a folderPage/Account -
In the
Page/Account- folder createCustomLogoutModelModel-class (not a Razor page !!!) with a code as shown bellow
Click to show the code
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
namespace rupbes.Abp2Keycloak.Web.Pages.Account
{
[Volo.Abp.DependencyInjection.Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LogoutModel))]
public class CustomLogoutModelModel : LogoutModel
{
public override async Task<IActionResult> OnGetAsync()
{
Claim? clm = this.User?.FindFirst("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod");
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentitySecurityLogActionConsts.Logout
});
await SignInManager.SignOutAsync();
if ("OpenIdConnect".Equals(clm?.Value, StringComparison.OrdinalIgnoreCase))
{
return new RedirectResult("https://kc.rupbes.by:8445/realms/rupbes.test/account");
}
if (ReturnUrl != null)
{
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
if (await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
{
return RedirectToPage("/Account/Login");
}
return RedirectToPage("/");
}
}
}- after logout it will show Keycloak account page
- start cmd and run the command:
abp new rupbes.AbpAng01 --template app --ui-framework angular --theme leptonx-lite --create-solution-folder --no-tests
- open
AbpAng01HttpApiHostModule.cs-file of therupbes.AbpAng01.HttpApi.Host-project and modifyprivate void ConfigureAuthentication(ServiceConfigurationContext context)as follows:
Click to show the code
private void ConfigureAuthentication(ServiceConfigurationContext context)
{
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true;
});
context.Services.AddAuthentication()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, "SSO with Keycloak", o =>
{
o.SignInScheme = IdentityConstants.ExternalScheme;
o.SignOutScheme = IdentityConstants.ApplicationScheme;
o.ClientId = "Mvc02";
o.ClientSecret = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY"; // copy Secret from keycloak console
o.Authority = "https://kc.rupbes.by:8445/realms/rupbes.test";
// default value == "/signin-oidc"
// Keycloak "Valid redirect URIs"-param must be set to "/signin-oidc"
o.CallbackPath = new PathString("/signin-oidc");
// Requests received on this path will cause the handler to invoke SignOut using the SignOutScheme.
//o.RemoteSignOutPath = new PathString("/Home/DoLogout"); // default value
// default value == "/signout-callback-oidc"
// Keycloak "Valid post logout redirect URIs"-param must be set to "/signout-callback-oidc"
// o.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
/// <summary>
/// The uri where the user agent will be redirected to after application is signed out from the identity provider.
/// The redirect will happen after the SignedOutCallbackPath is invoked.
/// </summary>
/// <remarks>This URI can be out of the application's domain. By default it points to the root.</remarks>
// o.SignedOutRedirectUri = new PathString("/Home/DoCleanUpCookie"); // default value
o.ResponseType = OpenIdConnectResponseType.Code;
o.UsePkce = true;
// o.Scope.Add("user.read");
// o.MapInboundClaims = false;
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("email");
o.Scope.Add("phone");
// o.Scope.Add("offline_access");
o.SaveTokens = true;
});
}-
open the
launchSettings.jsonfile and copy the value ofapplicationUrlwithout the last backslash. In our case, we have"applicationUrl": "https://localhost:44374", so we copyhttps://localhost:44374. -
in the keycloak console insert copied value for MVC02 app
- Access settings
- Root URL:
https://localhost:44374 - Home URL:
https://localhost:44374
- Root URL:
-
Note: Angular app has a
Home URL=http://localhost:4200, but we sethttps://localhost:44374(!!!) andClient authentication=ON(!!!)
- Access settings
-
Start
rupbes.AbpAng01.HttpApi.Host-app, start angular app and clock login. -
On the login page click SSO with Keycloak-button and login into Keycloak as testuser
- You will be back on Login page of the rupbes.AbpMvc01.Web-app
- click register
- logout from the app
- try to login with SSO with Keycloak-button click
- second time it will not ask for password.
- You will be back on Login page of the rupbes.AbpMvc01.Web-app
-
to remind the user about complete logout we have to override Abp logout page
-
for
rupbes.Abp2Keycloak.Webproject create a folderPage/Account -
In the
Page/Account- folder createCustomLogoutModelModel-class (not a Razor page !!!) with a code as shown bellow
Click to show the code
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Settings;
namespace rupbes.AbpAng01.Pages.Account
{
[Volo.Abp.DependencyInjection.Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LogoutModel))]
public class CustomLogoutModelModel : LogoutModel
{
public override async Task<IActionResult> OnGetAsync()
{
Claim? clm = this.User?.FindFirst("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod");
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentitySecurityLogActionConsts.Logout
});
await SignInManager.SignOutAsync();
if ("OpenIdConnect".Equals(clm?.Value, StringComparison.OrdinalIgnoreCase))
{
return new RedirectResult("https://kc.rupbes.by:8445/realms/rupbes.test/account");
}
if (ReturnUrl != null)
{
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
if (await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
{
return RedirectToPage("/Account/Login");
}
return RedirectToPage("/");
}
}
}- after logout it will show Keycloak account page