You can connect a JavaScript single-page application (SPA), including React, Angular, Vue, or framework-free JavaScript, to MDriven Turnkey REST ViewModels by allowing its browser origin, validating its JSON Web Token (JWT), and reusing the Turnkey login cookie.
What this integration does
An SPA runs in the user's browser and calls a Turnkey REST endpoint such as:
http://localhost:5052/TurnkeyRest/Get?command=RestExample
For a cross-origin call, Turnkey must handle three separate concerns:
| Concern | Why you need it | Turnkey configuration |
|---|---|---|
| CORS (Cross-Origin Resource Sharing) | The browser must be permitted to call Turnkey from the SPA's origin. | Allow each trusted SPA origin through the prescribed CORS model pattern. |
| JWT authentication | Turnkey must validate the token issued by your identity provider and decide whether its user may sign in. | Configure SysExternalJWTDefinition keys and the username-acceptance logic. See Documentation:Authenticate with a jwt.
|
| Session cookie | Later REST calls can run as the signed-in Turnkey user without sending a new token each time. | When Turnkey accepts a JWT user, it looks up the matching SysUser and returns a login cookie. Browser requests must include credentials to reuse it.
|
The JWT is not enough on its own to grant access to data. Your REST-enabled ViewModel must still enforce its access groups. A user-specific ViewModel commonly uses SysSingleton.oclSingleton.CurrentUser and an access group such as IsLoggedIn. See HowTos:Call Turnkey REST from Javascript.
Before you begin
Prepare the following values before configuring the model:
- The SPA origin or origins. An origin includes scheme, host, and port. For example,
http://localhost:62031is different from another localhost port. - The REST endpoint and ViewModel command. This page uses
RestExampleas an example. - The audience value issued for your application by the identity provider.
- The identity provider's published signing keys. Each key has a key identifier (
kid), modulus (n), and exponent (e). - A Turnkey
SysUserwhose email matches the username returned from JWT processing. JWT login looks up an existing user; it does not create one.
If you use the Microsoft SPA example, register the application as an SPA, not only as a web application. The original example used an Azure AD application registration and received an idToken in JWT format.
Configure Turnkey
1. Allow the SPA origin through CORS
Create a KnownExternalApp object for every origin from which the SPA is served.
- Set
Originto the SPA origin, for examplehttp://localhost:62031. - Set
Audienceto the application identity from the identity provider. - Set
IsOktotrue. - Repeat for every development, test, or production origin that must call Turnkey.
Use the CORS method in the prescribed model pattern to allow only an origin represented by an approved KnownExternalApp. The example logic is:
if KnownExternalApp.allinstances->exists(x | (x.Origin = org) and (x.IsOk)) then
true -- allow this app to call Turnkey
else
false
endifDo not allow every origin. Browser credential handling requires specific allowed origins; an unrestricted origin policy does not work with login credentials.
2. Add identity-provider signing keys
Turnkey uses SysExternalJWTDefinition objects to validate tokens signed by the external identity provider.
- Obtain the provider's published signing keys. For the Azure AD example, the keys are available at the Azure AD v2.0 key discovery endpoint.
- For each distinct
kidreturned by the provider, create oneSysExternalJWTDefinition. - Copy the key's
nvalue to the definition'sModulusattribute. - Copy the key's
evalue to its exponent attribute. - Make the
Modulusattribute long enough to store the complete value. The example uses a length of 500 characters rather than 255.
Identity providers can publish multiple keys. Create a definition for each unique kid that Turnkey must recognize.
3. Accept the token audience and map the user
Implement SysExternalJWTDefinition.AcceptAndTransformUserName(user:string;audience:String):string so it returns a valid Turnkey username only for an approved audience. Return an empty string to deny the login.
if KnownExternalApp.allinstances->exists(x | (x.Audience = audience) and (x.IsOk)) then
user -- accept users from this audience when they exist in Turnkey
else
'' -- reject the user
endifYou can also transform user when necessary to match the email pattern used in SysUser.Email. When this method returns a nonempty value, Turnkey looks upânot createsâthe SysUser with that name and marks that user as logged in. Turnkey then places a session cookie in the response for later REST calls.
For the JWT validation and user-mapping behavior, see Documentation:Authenticate with a jwt.
4. Create a matching Turnkey user
Create or register an account in the Turnkey application using the same email address that the identity provider supplies to AcceptAndTransformUserName.
For example, if the SPA signs in as person@example.com, the corresponding Turnkey SysUser must use person@example.com. A token for an email that has no matching Turnkey user does not give access.
Call a Turnkey REST endpoint from the SPA
The following example obtains an idToken from the SPA's existing authentication code, sends it as a bearer token to Turnkey, and asks the browser to include stored credentials on the request.
function readTurnkey() {
getTokenPopup(loginRequest)
.then(response => {
const headers = new Headers();
const bearer = `Bearer ${response.idToken}`;
const endpoint =
'http://localhost:5052/TurnkeyRest/Get?command=RestExample';
headers.append('Authorization', bearer);
const options = {
method: 'GET',
headers: headers,
credentials: 'include'
};
console.log('request made to Turnkey API at: ' +
new Date().toString());
fetch(endpoint, options)
.then(response => response.json())
.then(data => updateUI(data, endpoint))
.catch(error => console.log(error));
})
.catch(error => console.error(error));
}
credentials: 'include' is important when the browser must send the Turnkey session cookie. The equivalent jQuery setting is xhrFields: { withCredentials: true }.
A minimal result display can serialize the JSON response:
try {
profileDiv.innerHTML = JSON.stringify(data);
}
catch (err) {
profileDiv.innerHTML = data.toString();
}
Test the complete flow
- Start the Turnkey server with the model containing the REST-enabled
RestExampleViewModel and the CORS/JWT configuration. - Start the SPA and open its application URL. In the original sample, the UI was reached at
your-local-address/app3. - Sign in through the SPA's identity provider.
- Select the action that calls
readTurnkey(). - Confirm that the REST response is displayed by the SPA.
- Verify the same ViewModel in Turnkey. In the example, it is available at
http://localhost:5052/Turnkey/AngularApp#/RestExample/$null$. - Test with an email that does not match a Turnkey account, and test without being logged in. These requests must not return user-protected data.
If your Turnkey server is not running at localhost:5052, update the SPA endpoint. If the SPA URL changes, update the corresponding KnownExternalApp.Origin value as well.
Diagnose common failures
| Symptom | Likely cause | Check |
|---|---|---|
The browser reports a CORS error or the JavaScript request only reports error.
|
The SPA origin is not allowed by Turnkey. | Confirm that a KnownExternalApp has the exact origin and IsOk is true.
|
| Turnkey rejects the JWT login. | The audience is not approved, the signing key is missing, or the username mapping returns an empty string. | Confirm KnownExternalApp.Audience, the relevant kid definition, modulus/exponent values, and AcceptAndTransformUserName.
|
The request returns AccessDenied.
|
The REST ViewModel's access control blocks the current user. | Confirm that the user is logged in to Turnkey and belongs to the required access group. |
| The request is authenticated once but later calls appear anonymous. | The browser is not sending the session cookie. | Use credentials: 'include' with fetch, or xhrFields: { withCredentials: true } with jQuery, and confirm the exact CORS origin configuration.
|
| A signed-in identity-provider user cannot access Turnkey data. | No matching Turnkey SysUser exists.
|
Register or create the Turnkey account with the email returned by the username-mapping method. |
