421.7 Abp microserices and Keycloak - chempkovsky/CS82ANGULAR GitHub Wiki
- Only security aspects will be discussed and implemented.
- In this article we will setup angular app
- We are currently using:
- MS Visual studio 22 (Community Edition)
- Volo.Abp.Studio.Cli v.1.1.1 Community Edition
- Volo.Abp.Cli v.9.2.3 Community Edition
- ABP Studio v.1.1.1 Community Edition
- Keycloak v.26.3.0
- Redis v=7.0.15
- modify
rupbes.tstapp\angular\projects\dev-app\src\environments\environment.ts-file as follows
Click to show the code
import { Environment } from '@abp/ng.core';
const baseUrl = 'http://localhost:4200';
export const environment = {
production: false,
application: {
baseUrl: 'http://localhost:4200/',
name: 'tstapp_angular',
logoUrl: '',
},
oAuthConfig: {
issuer: 'https://kc.rupbes.by:8445/realms/rupbes.tstrealm',
redirectUri: baseUrl,
clientId: 'tstapp_angular',
responseType: 'code',
scope: 'offline_access tstapihost_scope tstauth_scope tenantid roles email phone',
requireHttps: true
},
apis: {
default: {
url: 'https://localhost:54635/',
rootNamespace: 'rupbes.tstapp',
},
tstapp: {
url: 'https://localhost:44323',
rootNamespace: 'rupbes.tstapp',
},
},
} as Environment;-
We have to set Client authentication: OFF
-
In keycloak console
- click
Manage realms-menu - click
rupbes.tstrealm - click
Clients-menu - click
Create Client-button
- click
-
On the
Create client-page- Client type: OpenID Connect
- Client ID: tstapp_angular
- Client authentication: OFF
- Authorization: OFF
- Authentication flow: Standard flow
-
Root URL: http://localhost:4200 (we copied this value from
environment/application/baseUrl-prop above) -
Home URL: http://localhost:4200 (we copied this value from
environment/application/baseUrl-prop above) -
Web origins: http://localhost:4200 (we copied this value from
environment/application/baseUrl-prop above) - Valid redirect URIs: http://localhost:4200
- Valid post logout redirect URIs: http://localhost:4200
- click
Save-button
-
Add the scopes below to
tstapp_angular-app: -
Add admin role
- assign this
admin-role to thetstadmin-user
- assign this
- run
rupbes.tstapp.Auth.Host-app. It will create tstadmin-user - run angular-app (do not stop rupbes.tstapp.Auth.Host-app)
- in the folder
rupbes.tstapp\angularexecute the command:ng serve -o
- in the folder
- login as
tstadmin-user and password that was defined in keycloak (you can redefine password in keycloak for tstadmin-user. It must be different from 1q2w3E* for correct testing)
-
tenantid scope must be defined as
Default(not as Optional). - If
tenantid-scope is optional:- create a tenant in
Abp-app - in keycloak console create
tenant user - in keycloak console assign
Admin-role totenant user. We obtaintenant admin - Modify scope: 'offline_access tstapihost_scope tstauth_scope roles email phone' in
environment.ts-file (removetenantidfrom the list) - login as
tenant admininabp-app and you will get Host Admin Rights instead of Tenant Admin Rights- take a look at TenantResolver
- create a tenant in
- We have no errors for the host user (tenantid==null), but for the tenant user (tenantid != null) angular application throws exception during 'log out':
Access to XMLHttpRequest at 'https://kc.rupbes.by:8445/realms/rupbes.tstrealm/.well-known/openid-configuration' from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field __tenant is not allowed by Access-Control-Allow-Headers in preflight response.
- We tried to rewrite
OAuthApiInterceptor(it is Abp Interceptor) but to no avail. Refreshing browser page throws an exception.
Click to show the code
// providers: [
// ...
// {
// provide: HTTP_INTERCEPTORS,
// useExisting: CustomOAuthApiInterceptor,
// multi: true
// },
// { provide: OAuthApiInterceptor, useClass: CustomOAuthApiInterceptor }
// ...
// ]
//
import { HttpEvent, HttpHandler, HttpHeaders, HttpRequest } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';
import {
HttpWaitService,
IApiInterceptor,
IS_EXTERNAL_REQUEST,
SessionStateService,
TENANT_KEY,
} from '@abp/ng.core';
import { OAuthApiInterceptor } from '@abp/ng.oauth';
@Injectable({
providedIn: 'root',
})
export class CustomOAuthApiInterceptor implements IApiInterceptor {
protected oAuthService = inject(OAuthService);
protected sessionState = inject(SessionStateService);
protected httpWaitService = inject(HttpWaitService);
protected tenantKey = inject(TENANT_KEY);
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.httpWaitService.addRequest(request);
const isExternalRequest = request.context?.get(IS_EXTERNAL_REQUEST);
// const newRequest = isExternalRequest
// ? request
// : request.clone({
// setHeaders: this.getAdditionalHeaders(request.headers),
// });
const newRequest = request.clone({
setHeaders: this.getAdditionalHeaders(request.headers),
});
return next
.handle(newRequest)
.pipe(finalize(() => this.httpWaitService.deleteRequest(request)));
}
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
const headers = {} as any;
const token = this.oAuthService.getAccessToken();
if (!existingHeaders?.has('Authorization') && token) {
headers['Authorization'] = `Bearer ${token}`;
}
const lang = this.sessionState.getLanguage();
if (!existingHeaders?.has('Accept-Language') && lang) {
headers['Accept-Language'] = lang;
}
this.sessionState.setTenant(null);
// console.log('this.sessionState.getTenant()=', this.sessionState.getTenant());
//const tenant = this.sessionState.getTenant();
//if (!existingHeaders?.has(this.tenantKey) && tenant?.id) {
// headers[this.tenantKey] = tenant.id;
//}
headers['X-Requested-With'] = 'XMLHttpRequest';
console.log('headers=', headers);
return headers;
}
}- we didn't waste time solving the problem with Keycloak
- Instead, we rewrote the
AbpApplicationConfigurationAppServiceclass- In the
rupbes.tstapp.Auth.Host.csproj-project createConfigurationService-folder - In the
ConfigurationService-folder createCustomAbpApplicationConfigurationAppService-class as follows:
- In the
Click to show the code
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using System;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending;
using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.Authorization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.Users;
using Volo.Abp.DependencyInjection;
namespace rupbes.tstapp.ConfigurationService
{
[Dependency(ReplaceServices = true)]
public class CustomAbpApplicationConfigurationAppService: AbpApplicationConfigurationAppService
{
public CustomAbpApplicationConfigurationAppService(IOptions<AbpLocalizationOptions> localizationOptions, IOptions<AbpMultiTenancyOptions> multiTenancyOptions, IServiceProvider serviceProvider, IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider, IPermissionDefinitionManager permissionDefinitionManager, DefaultAuthorizationPolicyProvider defaultAuthorizationPolicyProvider, IPermissionChecker permissionChecker, IAuthorizationService authorizationService, ICurrentUser currentUser, ISettingProvider settingProvider, ISettingDefinitionManager settingDefinitionManager, IFeatureDefinitionManager featureDefinitionManager, ILanguageProvider languageProvider, ITimezoneProvider timezoneProvider, IOptions<AbpClockOptions> abpClockOptions, ICachedObjectExtensionsDtoService cachedObjectExtensionsDtoService, IOptions<AbpApplicationConfigurationOptions> options) : base(localizationOptions, multiTenancyOptions, serviceProvider, abpAuthorizationPolicyProvider, permissionDefinitionManager, defaultAuthorizationPolicyProvider, permissionChecker, authorizationService, currentUser, settingProvider, settingDefinitionManager, featureDefinitionManager, languageProvider, timezoneProvider, abpClockOptions, cachedObjectExtensionsDtoService, options)
{
}
protected override CurrentTenantDto GetCurrentTenant()
{
return new CurrentTenantDto()
{
Id = null, // CurrentTenant.Id,
Name = null, // CurrentTenant.Name!,
IsAvailable = false //CurrentTenant.IsAvailable
};
}
}
}- So,
- Angular application knows nothing about the tenant of the current user.
- But it work as expected.
- Back-end apps work as expected as well (rupbes.tstapp.Auth.Host and rupbes.tstapp.HttpApi.Host).
- Backend applications have information about the current user's tenant, and that's more than enough.
- The MVC application also works as expected (rupbes.tstapp.Web.Host) even without knowing about the tenant of the current user.
- Angular application knows nothing about the tenant of the current user.
- It'll be a prototype pattern for production project.
- This will completely close the Problems with the NGINX
- important