Taly OAuth PoC (using PKCE)

This package exports helpers that can be used to take care of OAuth authentication in an application.

1. OauthModule

To benefit from this package 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 expected config per IdP 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
  appTokenUrl: 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;
  // 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;
}

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.

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

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

2. OauthPfeIntegrationModule

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:

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

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

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

2.3. Usage with client state

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

{
  "navConfiguration": {
    "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"
          }
        ]
      }
    ]
  },
  "appConfiguration": {
    "pfeConfig": {
      "urlParametersInState": [
        {
          "key": "userType",
          "stateKeyExpression": "$.clientState.userType"
        },
        {
          "key": "state",
          "stateKeyExpression": "$.oauth.state"
        }
      ]
    }
  }
}

results matching ""

    No results matching ""