Twinfinity Authentication makes it possible to authenticate towards Twinfinity.
Twinfinity Session is a high level API to authenticate users towards Twinfinity.
OpenID Connect Client is a lower level API to authenticate towards Twinfinity.
Neither Twinfinity Session nor OpenID Connect Client instances should be stored in such a way that they are easily accessed if possible. An instance of either can be used to make requests towards Twinfinity with the logged in users identity, so some care needs to be taken.
For the typical use case, when using @twinfinity/authentication with @twinfinity/core, the following pattern is suggested.
async function initializeBimApi() {
// Establish a Twinfinity session
const session = await TwinfinitySession.establish({
clientId: '<client-id>', // Client id is given by Twinfinity
openIdProviderUrl: '<idp-url>'
});
// Add a listener that will notify you when the session is terminated. The session should
// typically be automatically refreshed, but it is possible for it to break
session.registerOnSessionTerminatedCallback(async () => {
// Let the user know that the session has been terminated, perhaps through a modal window
const modal = await showSessionTerminatedModal();
// This will re-establish the session by redirecting the browser the same way as {@link establish} does.
modal.button.onClick(async () => await session.reEstablish());
});
// Restore the original url from before the authentication flow began.
session.recoveredApplicationState.restoreOriginalUrl();
// Get the user's id_token
const identityToken = await session.getIdentityToken();
await showUserInfo(identityToken);
// Use the session to create an authenticated BimApi instance
return await BimApi.create('viewer', '<api-url>', {
session
});
}
window.onload(async () => {
const bimApi = await initializeBimApi();
// do stuff...
});
In cases where the application only requires login to Twinfinity in scenarios, login can be performed without reloading the application. Due to security and privacy features of current web browsers, this becomes a bit more involved.
If the user already has a valid session towards Twinfinity this can be used to establish a session without user interaction.
const sessionOptions: TwinfinitySessionOptions = {
clientId,
openIdProviderUrl,
redirectUri
};
const twinfinitySession = await TwinfinitySession.establishEmbedded(sessionOptions);
This will create a hidden iframe in which the login flow will be performed. Many modern browsers will however classify the session cookies set by Twinfinity or other identity providers as third party cookies when loaded in a cross origin iframe. Without these cookies the user will not be able to establish their session.
If the hidden login flow fails and the user has not allowed sending cookies, the call to establishEmbedded will fail with the error no_cookie_access. Requesting cookie access is done using Storage Access API and needs to be triggered by user action. When this is needed an iframe should be passed to establishEmbedded. This iframe should be placed together with clarifying information to the user since Twifinity authentication only will create a button which triggers the access request.
const embeddedLoginContainer = // Element containing additional information to the user
const loginFrame = // Iframe that will contain the button if needed
try {
twinfinitySession = await TwinfinitySession.establishEmbedded(sessionOptions).catch(async (e) => {
if (e.error === 'no_cookie_access') {
embeddedLoginContainer.classList.remove('hidden');
return await TwinfinitySession.establishEmbedded({
...sessionOptions,
loginFrame: loginFrame,
cookieAccessPromptText: 'Allow session cookies',
cookieAccessPromptStyle: {
backgroundColor: '#303030',
color: '#FFFFFF'
}
});
} else {
throw e;
}
});
console.info('Silent authentication successful');
} catch (e) {
console.info('Silent authentication failed, falling back to interactive authentication', e);
} finally {
embeddedLoginContainer.classList.add('hidden');
}
Even when the user has allowed access to third party cookies they might be logged out or needed to provide multi factor authentication again. In this case the application either has to perform a top level redirect to perform the login or use a popup. If a popup is used it should be done on user event so that it is not blocked by the browser.
const loginRequestContainer = // Element containing login button
try {
twinfinitySession = await new Promise((resolve, reject) => {
const loginRequestContainer = document.querySelector('.login-request-container')!;
loginRequestContainer.classList.remove('hidden');
loginRequestContainer.querySelector('button')!.addEventListener('click', async () => {
try {
console.info('Establishing session with popup');
const session = await TwinfinitySession.establishUsingPopup(sessionOptions);
loginRequestContainer.classList.add('hidden');
resolve(session);
} catch (e) {
console.error('Error establishing session', e);
loginRequestContainer.classList.add('hidden');
reject(e);
}
});
});
} catch (e) {
console.error('Error establishing session', e);
}