OAuth Integration (using PKCE)

This package exports helpers and a TALY plugin that can be used to take care of OAuth authentication in generated applications. Manually-maintained applications are also supported, regardless of whether they are PFE-driven or not.

1. The TALY oauth configuration

When setting up TALY oauth in your application, you need to provide an array of configurations, one per IdP. Each configuration object looks like this:

export interface OauthConfig {
  // set to `true` if you want this configuration to be used when calling `authorize` or `retrieveToken` without a specific config
  default?: boolean;
  // name to identify this IdP configuration. Used to find the correct configuration when calling `authorize` or `retrieveToken` with a configuration name.
  name: string;
  // the application's client id of the OAuth Identity Provider (IdP)
  clientId: string;
  // the url to the IdP's authorize endpoint, can usually be found in your IdP configuration
  authorizeUrl: string;
  // the url where tokens can be retrieved from, can usually be found in your IdP configuration
  tokenUrl: string;
  // the url where the user should be redirected to after a successful login.
  // Needs to be set if `appTokenPageIdOrInternalPath` isn't.
  // Will be overwritten by `appTokenPageIdOrInternalPath` if both are set
  appTokenUrl?: string;
  // The internal path of the generated journey, which TALY will use to infer the url where
  // the user should be redirected to after a successful login.
  // Needs to be set if `appTokenUrl` isn't. Will overwrite `appTokenUrl` if both are set.
  appTokenPageIdOrInternalPath?: string;
  // if set, this URL will be used on logout to revoke the access and refresh tokens. Only set this if your IdP supports revocation.
  revokeUrl?: string;
  // if set, this URL will be used on logout to end the session with the IdP. Only set this if your IdP supports ending sessions.
  endSessionUrl?: string;
  // the scope that the authentication should grant; depending on the IdP, this will usually be "openid offline_access"
  scope: string;
  // a list of ACR values that are passed to the IDP during the initial /authorize request
  // see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for details
  acrValues?: string[];
  // the bff base url. Only requests to endpoints under this URL will receive the auth header
  bffBaseUrl: string;
  // by default the access token will be renewed before a request is fired if it expires in less than 5 minutes. With this parameter you can adjust this threshold. Specify in milliseconds
  refreshBeforeExpiryThreshold?: number;
}

1.1. The 'appTokenPageIdOrInternalPath' option

As described above, you can use this option as an alternative to appTokenUrl to configure the URL where the user should be redirected to, after a successful login. This option is specially intended to support journeys that are deployed once but integrated in different places. You provide the part corresponding to the internal path of your application, and TALY will infer the full URL.

The value of the "appTokenPageIdOrInternalPath" field

If your app is a TALY-generated journey, make sure that the value of the field matches the corresponding page id in your journey, e.g., "token".

For manual apps, the value of the appTokenPageIdOrInternalPath field might be the page id, but it could also be a nested path in your app, like "new-claim/token".

The "TalyRoutingUtilsService"

For the appTokenPageIdOrInternalPath to work, you need to provide a TalyRoutingUtilsService implementation that can be used to infer the full URL.

If you are on a generated journey, you are all set! 🎉 The TalyRoutingUtilsService is already provided by the TALY journey.

On the other hand, if you are using TALY oauth in a manually maintained app, you need to provide the TalyRoutingUtilsService yourself. If you are in a manual PFE application, you need to add this code to your app:

providers: [
  //...
  {
    provide: TalyRoutingUtilsService,
    useClass: PfeRoutingUtilsService
  }
];

We got you also covered if you are in a manual non-PFE application. You can either provide the default TALY provider:

providers: [
  //...
  TalyRoutingUtilsService
];

or, if you need a different implementation, you can write and provide your own:

providers: [
  //...
  {
    provide: TalyRoutingUtilsService,
    useClass: MyAwesomeRoutingUtilsService
  }
];

2. Usage in a non-PFE application

To benefit from the TALY oauth package in a non-PFE application you need to import the OauthModule in your main module. To pass the necessary configuration you can either import the module as OauthModule.forRoot() and pass an object with an array of configurations (one per identity provider (IdP)) as the only argument, like forRoot({ configurations: [config1, config2] }) or you can provide an OAUTH_CONFIGURATIONS InjectionToken that holds the array of configurations.

The OauthModule adds providers for a service and an interceptor that are described in more detail in the next subsections. To properly integrate the OauthService in a config-driven PFE application please see the section "PFE Integration" below.

2.1. OauthService

This service takes care of storing the authentication tokens and communicates with the OAuth backend. The most important functions that this service exposes are authorize() and retrieveToken():

  • authorize(oauthConfigName?: string, clientState?: Object)
    This function will redirect your user to the configured OAuth login form where he/she can login. After a successful login the auth page is expected to redirect the user to the appTokenUrl that you passed via the OauthConfig. The appTokenUrl should lead to a page in your app that calls the retrieveToken() function. See below. The optionally given clientState object will be encoded and passed to the IDP. The app will receive this state as the state query parameter upon returning back to the application.

  • retrieveToken(oauthConfigName?: string)
    This function will use the code that got passed from the auth form (as query parameter) to retrieve an access token, an ID token, and a refresh token to authorize subsequent backend requests. It is important to call this method immediately after the user returns to your application after he/she logged in. Any further navigation inside your app will most likely remove the query parameters from the URL and without them the tokens can't be retrieved anymore.

  • logout(oauthConfigName?: string)
    This function will log the user out of the IdP referenced by the OAuth configuration. Most importantly, it will remove all IdP-related entries from the sessionStorage. On top of that, if a revokeUrl and an endSessionUrl are configured, both endpoints will be called to revoke the access and ID tokens and end the session with the IdP.

  • decodeClientState(stateString: string)
    This function decodes any previously encoded client state that got passed to the IDP in an authorize() call. It will return the object that got encoded or an empty object if no encoded client state could be found.

Both methods take the oauthConfigName as an optional parameter. If set, they will use the corresponding configuration (identified using the name). If not set, they will take the default configuration.

2.2. OauthInterceptor

This interceptor can be used to ensure that each outgoing BFF Request will contain an Authentication header that includes the current access token. If the access token is about to expire this interceptor will take care of fetching a new access token using the refresh token. The OauthModule already adds a provider for this interceptor so you don't need to manually provide it.

The interceptor will automatically pick the first matching OAuth configuration (i.e. the IdP) by bffBaseUrl and the respective access token. It finds the first configuration whose bffBaseUrl matches the request URL and that has an access token set (i.e. for which authorize has been called), and then uses that access token for the Authorization header.

3. Usage in a PFE application

You can additionally import the OauthPfeIntegrationModule to easily use the OauthModule in any PFE application. The OauthPfeIntegrationModule provides the same static forRoot() function as the OauthModule that you can use to pass the configurations. It will take care of properly importing the OauthModule. The integration module registers these PFE actions that you can use in the PFE config:

3.1. Available PFE Actions

  • OAUTH_AUTHORIZE
    This PFE action calls the authorize() function on the OauthService. You can pass a json expression as clientStateExpression that will be read from the current state and will be encoded and put in the state query param. This query parameter will be available after the IDP redirects back to your application. Use appConfiguration.pfeConfig.urlParametersInState to store any query parameters in the PFE state to use it for your client state. See the usage note here.

  • OAUTH_GET_TOKEN
    This PFE action should be called from the page in your application that the user lands on after a successful login. See the section about OauthService.retrieveToken() above for the reasons. This action also stores the retrieved tokens at the key oauth in the PFE store in the following format if you want to use them in your application: { accessToken: string; idToken: string; refreshToken: string; expiresIn: number; }

  • OAUTH_LOGOUT
    This PFE action will take care of logging out the current user by calling logout() on the OauthService.

  • OAUTH_DECODE_CLIENT_STATE
    This PFE action needs to receive an encodedClientStateExpression as well as a clientStateDestinationExpression. It reads an encoded client state from the state value at encodedClientStateExpression and writes the decoded state into the state at clientStateDestinationExpression.

  • OAUTH_LOGOUT
    This PFE action will take care of logging out the current user by calling logout() on the OauthService.

For both actions, you can pass the oauthConfigName parameter to explicitly set which IdP configuration to use. If you omit the oauthConfigName, the default configuration will be used.

See the Usage section for an example to achieve this in PFE-driven applications.

3.2. Usage

You can use these actions in your pfe.json. One possible example configuration looks like shown below. The OAUTH_AUTHORIZE action is triggered when the user leaves the login page. The appTokenUrl that is passed via the OauthConfig in this case needs to point to your app's token page to ensure that the OAUTH_GET_TOKEN PFE action is run immediately after a successful login.

🧩 For more details and an implementation example see the oauth recipe that we prepared.

// Navigation Configuration
{
  "pages": [
    {
      "pageId": "login",
      "onPageLeaveActions": [
        {
          "type": "OAUTH_AUTHORIZE",
          "oauthConfigName": "My standard Idp"
        }
      ]
    },
    {
      "pageId": "token",
      "allowDirectNavigationWithoutState": true,
      "onPageEnterActions": [
        {
          "type": "OAUTH_GET_TOKEN",
          "oauthConfigName": "My standard Idp"
        }
      ]
    }
  ]
}

3.3. Usage with Client State

This is an example about how to setup your application to be able to keep a client state while redirecting to and from the IDP

// Navigation configuration
{
  "pages": [
    {
      "pageId": "login",
      "onPageLeaveActions": [
        {
          "type": "OAUTH_AUTHORIZE",
          "oauthConfigName": "My standard Idp",
          "clientStateExpression": "$.clientState"
        }
      ]
    },
    {
      "pageId": "token",
      "allowDirectNavigationWithoutState": true,
      "onPageEnterActions": [
        {
          "type": "OAUTH_DECODE_CLIENT_STATE",
          "encodedClientStateExpression": "$.oauth.state",
          "clientStateDestinationExpression": "$.clientState"
        },
        {
          "type": "OAUTH_GET_TOKEN",
          "oauthConfigName": "My standard Idp"
        }
      ]
    }
  ]
}
// Additional global configuration
{
  "appConfiguration": {
    "pfeConfig": {
      "urlParametersInState": [
        {
          "key": "userType",
          "stateKeyExpression": "$.clientState.userType"
        },
        {
          "key": "state",
          "stateKeyExpression": "$.oauth.state"
        }
      ]
    }
  }
}

3.4. Usage as a TALY Plugin in a generated journey

The module OauthPfeIntegrationModule can be used as a TALY Plugin. With this plugin in your journey, you can use the snippets in the above sections in your journey's pfe.json file to set up a log-in page/flow.

Here is what a plugin configuration in your journey configuration could look like:

{
  "plugins": [
    {
      "package": "@allianz/taly-oauth/pfe",
      "version": "latest",
      "modules": [
        {
          "name": "OauthPfeIntegrationModule",
          "options": {
            "configurations": [
              {
                "default": true,
                "name": "default-auth",
                "authorizeUrl": "http://your-idp.com/authorize",
                "clientId": "U2q7XvFIBJVSlL3QIJ1ARjiIN4DZc9j9",
                "scope": "openid offline_access",
                "tokenUrl": "http://your-idp.com/token",
                "appTokenPageIdOrInternalPath": "token",
                "serviceActivatorEndpoint": "",
                "bffBaseUrl": "http://your-bff.com",
                "endSessionUrl": "http://your-idp.com/end_session",
                "revokeUrl": "http://your-idp.com/revoke"
              }
            ]
          }
        }
      ]
    }
  ],
  "...": "..."
}

⚠️ Note that the @allianz/taly-oauth Plugin should not be used if your generation-target is module. In these cases, the parent app needs to provide a log-in mechanism and the necessary HTTP Interceptor to authenticate the calls made from within your journey.

4. Further Resources

The TALY OAuth library uses the OAuth2.0 and OpenID Connect (OIDC) protocols to communicate with IdPs. The reference documentation for these protocols provides more detailed information:

results matching ""

    No results matching ""