🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
Connecting javascript SinglePageApplications to Turnkey (SPA)
This page was created by Hans.karlsen on 2021-02-28. Last edited by Wikiadmin on 2026-07-29.

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:62031 is different from another localhost port.
  • The REST endpoint and ViewModel command. This page uses RestExample as 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 SysUser whose 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.

  1. Set Origin to the SPA origin, for example http://localhost:62031.
  2. Set Audience to the application identity from the identity provider.
  3. Set IsOk to true.
  4. 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
endif

Do 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.

  1. 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.
  2. For each distinct kid returned by the provider, create one SysExternalJWTDefinition.
  3. Copy the key's n value to the definition's Modulus attribute.
  4. Copy the key's e value to its exponent attribute.
  5. Make the Modulus attribute 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
endif

You 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

  1. Start the Turnkey server with the model containing the REST-enabled RestExample ViewModel and the CORS/JWT configuration.
  2. Start the SPA and open its application URL. In the original sample, the UI was reached at your-local-address/app3.
  3. Sign in through the SPA's identity provider.
  4. Select the action that calls readTurnkey().
  5. Confirm that the REST response is displayed by the SPA.
  6. Verify the same ViewModel in Turnkey. In the example, it is available at http://localhost:5052/Turnkey/AngularApp#/RestExample/$null$.
  7. 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.

See also