Angular SPA using Assisted Token Flow

Angular SPA using Assisted Token Flow

Code Examples / spa-integration

Overview

An example Angular application capable of acquiring an access token from the Curity Identity Server using the assisted token flow. The example code contains a Single Page Application for acquiring the token and an example API which can be called from the SPA using the access token.

Best Practice Update

For best results and strongest security it is no longer recommended to implement OAuth for SPAs solely in Javascript. See our resources on the Token Handler Pattern, for using only the latest secure cookies in the browser without losing any of the benefits of an SPA architecture.

Configure the Identity Server

In the Curity Identity Server, create an OAuth client with the following details, and also ensure that at least one working authenticator is configured against the client:

  • Client ID: client-assisted-example
  • Capabilities: Assisted Token Flow + Implicit Flow
  • Allowed Origins: http://localhost:4200
  • Redirect URI: http://localhost:4200
  • OAuth + OpenID Settings / Scope: openid

Configure the SPA

The Angular SPA can be downloaded from the GitHub link on this page and will then run at http://localhost:4200 by default. Edit the environment.ts file in the SPA and set details to match your environment:

export const environment = {
  production: false,
  issuer: 'https://localhost:8443/',
  clientId: 'client-assisted-example',
  apiUrl: 'http://127.0.0.1:8100',
  authServerOrigin: 'http://127.0.0.1:8100',
  openIdConfigurationUrl: 'oauth/v2/oauth-anonymous/.well-known/openid-configuration'
};

The SPA will use the Assisted Token Endpoint from the Curity Identity Server, and will download a library from a URL such as this at runtime:

  • https://localhost:8443/oauth/v2/oauth-assisted-token/resources/js/assisted-token.min.js

The SPA will then use the window.curity.token.assistant Javascript object to perform OAuth operations, via the AssistantService TypeScript class.

Run the SPA

Now that the setup is complete you can run npm install then npm start to start the server, then browse to the SPA at http://localhost:4200. When the page loads, it makes an initial full screen redirect to the Identity Server in order to act as a user gesture, so that SSO cookies are then persisted by the browser. This is done in the assistant.service.ts source file in the checkAuthorization method:

this.window.location.href = this.config.authorization_endpoint + `?response_type=id_token&scope=openid&client_id=${environment.clientId}` +
        `&redirect_uri=${this.window.origin}&prompt=none&nonce=${nonce}`;

Perform a Login

Next click the Login button to trigger sign in via an iframe, to prompt the end user to authenticate. The sample then uses secure iframing to invoke a login window without disrupting the current state within the SPA:

Angular Sign In

Triggering the login request only requires a single line of code when using the assisted token library, after which tokens are returned to the SPA:

this.assistantService.getAssistant().loginIfRequired().then((msg) => {
    this.userToken = this.assistantService.getAssistant().getAuthHeader();
}

Once login is complete, an authenticated session begins, and this is represented by an SSO cookie that the Identity Server issues to the SPA. This cookie is used by the assisted token library to retrieve tokens silently while the user’s authenticated session remains valid.

Call an API from the Angular SPA

After user sign in, the code sample provides a minimal screen as follows, to show the token returned from login and sent to the sample’s API:

Angular API Calls

The sample uses the Angular HTTP Client to call the API and note that the assisted token library manages overriding the XmlHttpRequest and adding the access token to the Authorization header in the correct manner.

callApi() {
    const tokenAssistant = this.assistantService.getAssistant();
    const isUserAuthenticated = tokenAssistant.isAuthenticated() && !this.tokenAssistant.isExpired();
    if (isUserAuthenticated) {
        this.http.get(environment.apiUrl + '/api').subscribe(
            this.apiResponse = response.data;
    }
    else {
        tokenAssistant.loginIfRequired().then((token) => {
            this.callApi();
    }

The assisted token library also helps to deal with access token expiry conditions. If a token is expired or a 401 response is received from the API, the SPA can call loginIfRequired again to attempt to silently refresh the token, or to prompt the user to re-authenticate if the SSO cookie has expired.

API Cross Origin Request Handling

The Angular code example includes a simple NodeJS API which runs at http://localhost:8100 and is implemented in the server.js file. The API operation simply echoes the token back to the UI for display, but the API code shows how to set API response headers so that the browser is allowed to make cross origin calls to the API:

var origin = request.headers["origin"];
if (request.method === 'OPTIONS') {
    responseHeaders['Access-Control-Allow-Origin'] = origin;
    responseHeaders['Access-Control-Allow-Methods'] = "GET, HEAD, OPTIONS";
    responseHeaders['Access-Control-Allow-Headers'] = 'Authorization, WWW-Authenticate, Content-Type';
    responseHeaders["Access-Control-Allow-Credentials"] = "true";
    responseHeaders["Allow"] = "GET, HEAD, OPTIONS";
    response.writeHead(200, responseHeaders);
    response.end();
}
else {
    responseHeaders["Access-Control-Allow-Origin"] = origin;
    responseHeaders["Access-Control-Allow-Credentials"] = "true";
}

Lifecycle Events

The Angular SPA is only a basic sample to show how to fit the Assisted Token flow into a Angular coding style. For further details on dealing with lifecycle events, see the Javascript Assisted Token Sample, which covers aspects such as token refresh and multi tab browsing.

Conclusion

The Assisted Token Flow can be easily integrated into a Angular SPA, and adapted to the Angular coding style. For further programming details, see the Assisted Token Library API Reference.

Keep up with our latest articles and how-tos RSS feeds.