A12 Front-end application
This section is only available within mgm and A12's partners
If you're looking to build your own client application to connect with the TMT backend, this section will guide you.
One option is to use the A12 full-stack project template. This generally involves configuring your client application to communicate efficiently with the TMT backend.
1. Checkout A12 full-stack-project-template
This is used to determine the authorization of the request for specific project resource. See Project Permission for more information
const uaaClientConfigure: UaaClientConfiguration = {
serverURL,
automaticallyLogin: true,
store: config.store,
overrideClientConfigures: {
oidc: {
additionalRequestFilter: [new CustomUserRequestFilter(() => config.store?.getState())],
redirect_uri: window.location.href
}
}
};
export class CustomUserRequestFilter implements RequestFilter {
private readonly getAppReduxState: any;
constructor(getAppReduxState: any) {
this.getAppReduxState = getAppReduxState;
}
canHandleRequest(requestInit: RequestFilterPayload): boolean {
const { extendedData } = requestInit.payload;
return !extendedData?.unAuthorizeRequest;
}
doRequestFilter(requestFilterPayload: RequestFilterPayload): RequestFilterResult {
if (!requestFilterPayload || !requestFilterPayload.request) {
throw new Error("request init may not be falsy");
}
if (!requestFilterPayload.request.headers) {
requestFilterPayload.request.headers = new Headers();
}
const { headers } = requestFilterPayload.request;
const projectInstance = "Project-dm/10";
const subProjectInstance = "Project-dm/20";
appendHeader("project-id", projectInstance);
appendHeader("subproject-id", subProjectInstance); // Do not add this header if the working project is not a subproject
function appendHeader(name: string, value: string) {
if (headers instanceof Headers) {
headers.append(name, value);
}
}
requestFilterPayload.request.headers = headers;
return {
request: requestFilterPayload.request,
continue: true
};
}
}
2. Add custom UAA self configuration
UAA self configuration
SelfConfigure is an object that exposes information relevant to TMT Backend. This can help client applications automatically discover the necessary configuration details for connecting to Keycloak (which is used behind the scene by TMT)
A12 Client setup the security configure by using UaaClient service, it initally fetches the SelfConfigure when user open the website at the first time, which is done via the endpoint: /uaa-authentication/selfconfigure
However, TMT is designed as a multi tenant application, where each tenant has its own Keycloak Realm, the SelfConfigure must be different from the tenants. Therefore, we must customize the integration of UaaClient, by providing our own solution to fetch the SelfConfigure.
Fetch the SelfConfigure at the endpoint: /tmt-authentication/selfConfigure/{hostname}
with hostname is the domain of the current tenant (e.g demo.a12-tmt.com)
You might get the response as below (The response must match the type SelfConfigure defined by A12 UAA)
{
"tokens": [
{
"authorizationHeaderName": "Authorization",
"tokenType": "BEARER",
"generatedTokenHeaderName": null,
"generatedTokenExpirationHeaderName": null,
"allowCredentials": null
}
],
"oauth2": {
"tokenType": "BEARER",
"clientId": "user_management_spa_client",
"realmName": "demo",
"idpBaseUrl": "https://keycloak.a12-tmt.com",
"loginRedirectRelativeUrl": "",
"logoutRedirectRelativeUrl": "logout",
"silentRedirectRelativeUrl": "silent_renew.html"
}
}
This response then will be used as a value for paramter offlineSelfConfigure during the setting up of UaaClient
function setup() {
// ...
return {
config,
initialStoreActions: async () => {
await uaaIntegration(uaaClientConfigure);
}
};
}
export async function uaaIntegration(clientConfiguration: UaaClientConfiguration) {
const tmtSelfConfigure = (await fetchTmtSelfConfigure(clientConfiguration)) as SelfConfigure;
const uaaClientConfiguration = { ...clientConfiguration, offlineSelfConfigure: tmtSelfConfigure };
await UaaClient.init(uaaClientConfiguration);
// ...
}
async function fetchTmtSelfConfigure(uaaClientConfiguration: UaaClientConfiguration): Promise<any> {
const { serverURL, serverConnector, additionalRequestFilter, additionalResponseFilter } = uaaClientConfiguration;
const requestFilters = [...(additionalRequestFilter ?? [])];
const responseFilters = [...(additionalResponseFilter ?? [])];
const defaultServerConnector =
serverConnector ?? new RestServerConnector(serverURL, requestFilters, responseFilters);
ConnectorLocator.createInstance(defaultServerConnector);
const configureRequest = buildGetTmtSelfConfigureRequest();
const response = await (ConnectorLocator.getInstance().getServerConnector() as RestServerConnector).fetchData(
configureRequest
);
if (!(response instanceof Response)) {
throw new Error("Unexpected response type");
}
const result = await response.json();
if (isSelfConfigure(result)) {
return result;
}
LoggerFactory.getLogger("TMT/SelfConfigure").error("Invalid self-configure response");
return undefined;
}
function buildGetTmtSelfConfigureRequest(): RestRequestPayload {
const hostname = window.location.host;
return {
method: "GET",
relativeUrl: `/tmt-authentication/selfConfigure/${hostname}`,
customHeaders: [ACCEPT_JSON_HEADER]
};
}
If you try to get the selfConfigure from the default endpoint /uaa-authentication/selfconfigure
, you might get the response that differs from our custom endpoint, where we make it matches perfectly with SelfConfigure data type.
This default selfConfigure will be transformed implicitly by UaaClient via method UaaClient.reformatSelfConfigure to make it match with SelfConfigure data type.
By this way, the selfConfigure response returned from custom endpoint /tmt-authentication/selfConfigure/${hostname}
, that does not need to make any transformation to be used, but can inject as paramter into UaaClient immediately