Broadleaf Microservices
  • v1.0.0-latest-prod

Authorization

Overview

Broadleaf’s AuthenticationServices also provides an OAuth2 Authorization Server. These grant types are supported out-of-box:

  • Authorization Code Grant Type

  • Optionally with Proof-Key-for-Code-Exchange (since 1.8.2-GA)

  • Client Credentials Grant Type

  • Refresh Token (Rotation) Grant Type (since 1.4.0)

Authorization Code Grant Type

The Authorization Code Grant Type is used heavily especially with Broadleaf’s own client SPAs (single page applications): the Admin and the Customer Storefront Accelerator.

Sequence Diagram

The following sequence diagram represents a detailed OAuth2 Auth Code Grant Flow:

Authorization Code Grant Type Flow Diagram
Steps 5 & 6
Steps 9 & 10
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYWRtaW4iLCJvYXV0aDItcmVzb3VyY2UiXSwibWF4IjoxNTY0NjIxMjU5LCJ1c2VyX25hbWUiOiJtYXN0ZXJAdGVzdC5jb20iLCJzY29wZSI6WyJVU0VSIiwiTUVUQURBVEEiXSwiaXNzIjoiYWRtaW4iLCJHTE9CQUwiOnRydWUsIlNJVEVTIjpbXSwiZXhwIjoxNTY0NjIxMjU5LCJhdXRob3JpdGllcyI6WyJVU0VSIiwiTUVUQURBVEEiXSwianRpIjoiYTViNTYwNzAtOGEzZi00MWE0LTg2MjctZDljZTcxZjNhMDhhIiwiY2xpZW50X2lkIjoiYWRtaW4ifQ.ZfnCw0tOwU8dAa7-wAIJVdL5mEABg6qEBFBgPE0pfSadNTabJaFh24XXhj50_glFOqlPYTpI-x7YV_N6EF_5CqOdR2srIbLkLH0kENczFiQNzOILchurhBtKBQcibs0njrjcwvzpPkQGdwub6am1oILDpzh5jAw95aXxXhWHndBLpyWrSDr9hEnreW6WvQhmVmOqHqkLzJtejr9pR2e7bJDQUcTfPCERwBsQGu9h4zmAlhNJ0NZIgnlYw2ZQPlKEzHnBSWL1p11YQEG1NpGBjdWAvIyk9C7MJdXScN7cgV4yh4Pt4gsYbnS4BBPkqTaJJYcZpF3gYH7NEKu1GojVnw",
"token_type": "bearer",
"expires_in": 43199,
"scope": "USER METADATA",
"iss": "admin",
"GLOBAL": true,
"SITES": [],
"aud": ["admin", "oauth2-resource"],
"max": 1564621259,
"jti": "a5b56070-8a3f-41a4-8627-d9ce71f3a08a"
}
  • Cookies Set:

  • BLSID - Updates the cookie with a later expiration date

Note: The returned access_token can then be used to access endpoints that fall under the USER or METADATA scopes. This is done by including the access token as a Bearer Token request header.

Additional Notes
  • The initial authorization requests are enquiring about the USER & METADATA scopes. If another scope is used after being authenticated, a subsequent request must be made to the AuthService (SpringBoot Application) to ensure that the user has rights to that scope.

  • When attempting to render a component, we first confirm that the user has read access to the scopes that the component requires. This is done in the AdminServices (React Application) via makeCheckAccess which compares the user’s operationTypes for their accessible scopes against the READ operationType for the component’s required scopes.

  • If Operation B is in the same scope as Operation A & the access token is active, then the SPA will go directly to the service API instead of gathering a new access token.

Silent Authentication

After the user’s initial login (includes authentication & authorization for the USER & METADATA scopes), all subsequent authorization requests are done with a "silent authentication" which is an iframe-based mechanism. To follow the client-side logic for this flow, see AuthService#silentAuth, AuthService#renewAuthSilent, SilentAuthRenewal, & HiddenIframe in the AdminWeb project.

The app starts by calling AuthService#silentAuth which checks the cache to see if the user is already authorized. If not, then it calls AuthService#renewAuthSilent which initializes a SilentAuthRenewal instance. Under the covers, the SilentAuthRenewal leverages a HiddenIframe to call the AuthService (SpringBoot Application) to request authorization for the given scope(s). The response causes a redirect of the iframe to silent-callback.html with the authorization results as request params.

silent-callback.html simply takes the results & posts them as a message to the iframe’s parent window using window.parent.postMessage(). As a part of the HiddenIframe initialization, it registered an event listener on the parent window which handles the message. This will either return an authorization code or an error, which will cause a redirect to the login page if the errorType is LOGIN_REQUIRED.

In the AdminWeb project, see the usages of AuthService#silentAuthInterceptor - this function is included as an interceptor for all Axios ("Promise based HTTP client for the browser and node.js") calls to ensure that the user is authorized for that scope. Each time that we authorize the user for a new scope via the Axios interceptor, we are actually producing a new bearer token to be used for that scope or set of scopes.

Note: silent authentication is also attempted for steps 1 & 2 mentioned above, but the user is not yet authenticated, so they receive a response with error=login_required. This response informs the React app that the user should be taken to the login page.

Proof-Key-for-Code-Exchange (since 1.8.2-GA)

An additional enhancement that can be made to the standard Authentication Code Grant Flow is Proof-Key-for-Code-Exchange (PKCE, pronounce "pixie"). The purpose of this enhancement is to prevent Cross-Site-Request-Forgery (CSRF) and authorization code injection. This is accomplished by the client providing a secret proof key in the authorization and token requests. It was originally developed for use with mobile applications, but is now recommended for any public client like an SPA or apps using client secrets.

Tip
See https://www.rfc-editor.org/rfc/rfc7636 for the full spec.
Enabling PKCE

Since version 2.0, AuthorizedClient has a field to enable or disable PKCE for that client, which can be toggled in the admin. PKCE is enabled by default for new AuthorizedClients in version 2.0.

For 1.x versions, set property broadleaf.auth.security.pkce-enabled=true to enable PKCE for all public clients.

Authorization Request Flow with PKCE

The modifications to the standard flow are as follows:

  1. The client will generate and store a high-entropy, cryptographically random string called code_verifier for every OAuth authorization request.

  2. The client then creates a code_challenge from the code_verifier using a transformation method

    • The transformation must be a base-64 encoded, SHA-256 hash of the code_verifier if the platform supports SHA-256. Otherwise, it should use a no-op transformation so that the challenge and verifier are the same.

  3. This code_challenge must be added to the /oauth/authorize request as well as a code_challenege_method param specifying the transformation method used.

  4. The server must store the code_challenge and code_challenge method with the authorization code it generates so that they may looked-up again when the code is exchange for a token to validate the final request.

  5. After the server returns the redirect with a code, the client adds the code and code_verifier to the /oauth/token request to get an Access Token

  6. The server will then retrieve the code_challenge and code_challenge_method with the code and perform the specified transformation on the provided code_verifier. The code_challenge and transformed code_verifier must match to successfully complete the flow.

Client Credentials Grant Type: Service-to-Service Communication

This authorization server also supports the OAuth2 Client Credentials Grant as a way of authorizing service-to-service communications. This grant is used when there is no user principal involved, but a service needs to access a secured API on another service.

This flow should only be used for trusted sources as authorization is done directly against an authorized client using a client-id and a client-secret. This flows does not require user consent.

The Authorized Clients themselves need to define the info on what can be accessed through them (e.g. Scopes PRODUCT, CUSTOMER etc…​).

They can also define vendor restrictions through their Custom Attributes if necessary. To achieve this, the key of the attribute must be VENDOR, and the vendor restriction targets can be a single vendorRef or a list of vendorRefs. For example:

// Single Vendor
attributes: {
    "VENDOR": "vendorRef1"
}

// Multiple Vendors
attributes: {
    "VENDOR": ["vendorRef1", "vendorRef2", "vendorRef3"]
}

The first step to using this grant type is to define the client details:

broadleaf:
  auth:
    security:
      oauth2:
        registration:
          catalog:
            client-id: catalog (1)
            client-secret: $2a$08$2FeQ/74nASr9lE7dWRJXWeKW4gCsG6zVR69PUR1LzdJh87XemXvMm (2)
            authorized-grant-types: client_credentials (3)
  1. the client ID

  2. the client secret, encrypted by bcrypt (actual value: "catalog_secret")

  3. the authorized grant type must be "client_credentials"

Now that we have configured the authorization server with these client details, we can request a token using the client ID and client secret. The client credentials will need to be provided using Basic Authentication, for example:

POST /auth/oauth/token?grant_type=client_credentials HTTP/1.1
Host: https://auth.broadleaf.com
Authorization: Basic Y2F0YWxvZzpjYXRhbG9nX3NlY3JldA==

We will then expect back a token response:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYnJvYWRsZWFmLWF1dGhlbnRpY2F0aW9uIiwib2F1dGgyLXJlc291cmNlIl0sIm1heCI6MTU3MTgwNDA2OCwic2NvcGUiOlsiUFJPRFVDVCIsIkNBVEVHT1JZIl0sImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsImV4cCI6MTU3MTgwNDA2OCwiYXV0aG9yaXRpZXMiOlsiUkVBRF9QUk9EVUNUIiwiUkVBRF9DQVRFR09SWSIsIlBST0RVQ1QiLCJDQVRFR09SWSJdLCJqdGkiOiI0YzJjZDNlOC02ZjQxLTQ4ZmMtYTBmMy1iOTFiMzkzYmE0MmIiLCJjbGllbnRfaWQiOiJjYXRhbG9nIn0.gF2sPsIGCv6h0N7wLtThoDfFsE90VNUdq5ANNti7MQBDABh-VzA_05NSeWGpuj2YcGFEEQM1af5MG5nO8ZsAr_dDfo8bsCuAEyrBnK5XLAIpGwzJUayY33SuH9tExA2ynnWXbNSZM0n_nW1sujbCQ9Y9j6XW5GBOVtJlaSvWdSO069w-kTTIp90cBiBxIYnBZTPbsXxOt6xcWYXwKauYip0aK2ws7K3bIqi6HZR9OOotUxw4UZxByKH_qH9ZGnBrA82bQnWGxMoIp_Rtlo1IetFboFJvFQe40snX7qNix7mz2ujbEfT0P9oGE2p9N0HgbptkA73LDif8jS1pCQJV9w",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "PRODUCT CATEGORY",
    "aud": [...],
    "max": 1571804068,
    "iss": "broadleaf-authentication",
    "jti": "4c2cd3e8-6f41-48fc-a0f3-b91b393ba42b"
}

The access_token property contains the bearer token value that can be used to now authorize a request to the catalog service:

GET /catalog/products HTTP/1.1
Host: https://catalog.broadleaf.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYnJvYWRsZWFmLWF1dGhlbnRpY2F0aW9uIiwib2F1dGgyLXJlc291cmNlIl0sIm1heCI6MTU3MTgwNDA2OCwic2NvcGUiOlsiUFJPRFVDVCIsIkNBVEVHT1JZIl0sImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsImV4cCI6MTU3MTgwNDA2OCwiYXV0aG9yaXRpZXMiOlsiUkVBRF9QUk9EVUNUIiwiUkVBRF9DQVRFR09SWSIsIlBST0RVQ1QiLCJDQVRFR09SWSJdLCJqdGkiOiI0YzJjZDNlOC02ZjQxLTQ4ZmMtYTBmMy1iOTFiMzkzYmE0MmIiLCJjbGllbnRfaWQiOiJjYXRhbG9nIn0.gF2sPsIGCv6h0N7wLtThoDfFsE90VNUdq5ANNti7MQBDABh-VzA_05NSeWGpuj2YcGFEEQM1af5MG5nO8ZsAr_dDfo8bsCuAEyrBnK5XLAIpGwzJUayY33SuH9tExA2ynnWXbNSZM0n_nW1sujbCQ9Y9j6XW5GBOVtJlaSvWdSO069w-kTTIp90cBiBxIYnBZTPbsXxOt6xcWYXwKauYip0aK2ws7K3bIqi6HZR9OOotUxw4UZxByKH_qH9ZGnBrA82bQnWGxMoIp_Rtlo1IetFboFJvFQe40snX7qNix7mz2ujbEfT0P9oGE2p9N0HgbptkA73LDif8jS1pCQJV9w

That is all there is to it. There are different libraries that may help you with facilitating these requests server-side, e.g. Spring Security has a spring-security-oauth2-client library intended for client application. Of course, you can instrument these requests to retrieve and store access tokens on your own as well.

Refresh Token (Rotation) Grant Type (since 1.4.0)

Refresh token rotation is intended for usage with the auth code flow mentioned above. It represents an alternate strategy to silent authentication and does not require the use of an iframe or a session cookie to accomplish retrieving a new access token for a user. Nor, does it require prompting the user for authentication credentials to get the new access token. In this regard, the user experience can be very similar to the silent auth experience with very low friction.

Refresh token rotation is currently supported through the auth service API. The storefront starter uses silent authentication by default, but it does support refresh tokens since Auth SDK v1.5.3 (Release Train 1.8.2). The starter uses browser storage for the refresh token.

Before describing flow and configuration of refresh token rotation, it is useful to note recommendations regarding usage. Currently, silent auth is a more secure method of acquiring new access tokens than refresh token rotation. Therefore, while it may be used to secure SPA based flows, it is suggested that SPAs continue to leverage the silent auth technique. However, silent auth is generally not a feasible approach for mobile applications. In the case of mobile, it is suggested to leverage refresh token rotation.

Warning

Apple Safari has introduced ITP support, and Google Chrome intends to as well in future versions. This mechanism will prevent third party cookies from being sent by the browser to the API. This could impact Broadleaf silent auth if the Broadleaf auth service is not hosted behind the same gateway as the rest of the application. It is recommended to host the Broadleaf auth service behind the same gateway as the other components of the application. But, if this is not feasible, it is recommended to switch to refresh token rotation entirely, even for SPA usage.

Configuring Refresh Tokens

Authorized Clients can be configured to issue refresh tokens by adding "refresh_token" to the Grant Types field. In the admin, the Grant Types field is in the "Advanced" section when editing Authorized Clients.

In 2.0, this is all that is needed to enable refresh tokens. All access token requests will include a refresh token in the response, and refresh tokens will be rotated so that a new one is issued with every access token.

In 1.0, these steps are also necessary to get a refresh token with the access token response:

  • The application property broadleaf.auth.token.support-refresh-token-rotation must be set to true.

  • Include the OFFLINE_ACCESS scope during both auth code and token acquisition API calls.

Getting a Refresh Token

In the case of silent auth, the client application will attempt to use an access token and receive a 401 HTTP response if that token has expired. In such a case, the silent auth flow is used to retrieve a new access token. However, at this same point in time (401 HTTP response), an alternate refresh token rotation flow may be used instead.

To leverage this flow, ensure that refresh tokens are enabled in the Authorized Client as outlined above in Configuring Refresh Tokens.

In the 1.x line, for the /authorize and /token API calls, the scope param should include the additional label scope OFFLINE_ACCESS.

Note
In version 2.0, the OFFLINE_ACCESS scope is not required and has no effect. Returning refresh tokens is determined by Authorized Client settings.

Once the /token API is called, the JSON response will now not only contain an access_token value, but it will also contain a refresh_token value. This refresh token should be harvested and kept for subsequent one-time use to get a new access token once the current access token expires. Note, the refresh token is configured with a longer expiry to facilitate a comfortable timeline in which a new access token can be obtained without requesting the user to re-submit authentication credentials.

Sample

Response

{
  "access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnRfaWQiOiI1REYxMzYzMDU5Njc1MTYxQTg1RjU3NkQiLCJhcHBsaWNhdGlvbl9hY2Nlc3MiOnRydWUsImFkbWluX3VzZXJfaWQiOiItMyIsIm1heCI6MTYyMzE0MDAxOSwidXNlcl9uYW1lIjoidXNlckB0ZXN0LmNvbSIsImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsInRlbmFudF9hY2Nlc3MiOnRydWUsImNsaWVudF9pZCI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiTUVUQURBVEEiLCJVU0VSIl0sImF1ZCI6WyJicm9hZGxlYWYtYXV0aGVudGljYXRpb24iLCJvYXV0aDItcmVzb3VyY2UiXSwiZW1haWxfYWRkcmVzcyI6InVzZXJAdGVzdC5jb20iLCJmdWxsX25hbWUiOiJHbG9iYWwgVXNlciIsInVzZXJfdHlwZSI6IkFETUlOIiwidXNlcl9pZCI6Ii0zIiwic2NvcGUiOlsiTUVUQURBVEEiLCJVU0VSIl0sImV4cCI6MTYyMzA5NjgyMSwianRpIjoiOTMzOGIyYjItYjZiZS00NjZjLWJkYmEtNDZlODJmOTMzNmUzIn0.Ek18VTH3EU2t9cFKpxAEJR4GkE9UzW7JMB3qICXbxxfwqNwJyoh96yfrgQA-cfQHqiKqn0v9jW0thFStmI7rTneIp4_i-e2Um9Bb-rEIrNNO7qxQMCeFwmHk08mY572D82AtZItG93P0E7XNrxXYjzxFqVU8dhMZSg58Q_cGkPAkbh3DOL8cBNTYe2RZkkMp3ju9DZOo3Z10E3EMPQ7fQSEVjZeX-kUyqeDj_QSu1JYZEi3R6iKWsySgzZJJgK1K91FRnKcz0LR_g1MoejZyUG6746EhgeRVIqr-zGSU1OrfzGJfXZy96uPdFxDLwam8UHkz0E0JfmTdJLQM7E8F_g",
  "token_type":"bearer",
  "refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnRfaWQiOiI1REYxMzYzMDU5Njc1MTYxQTg1RjU3NkQiLCJhcHBsaWNhdGlvbl9hY2Nlc3MiOnRydWUsImFkbWluX3VzZXJfaWQiOiItMyIsIm1heCI6MTYyMzE0MDAxOSwidXNlcl9uYW1lIjoidXNlckB0ZXN0LmNvbSIsImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsIm9pZCI6bnVsbCwidGVuYW50X2FjY2VzcyI6dHJ1ZSwiY2xpZW50X2lkIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJNRVRBREFUQSIsIlVTRVIiXSwiYXVkIjpbImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsIm9hdXRoMi1yZXNvdXJjZSJdLCJlbWFpbF9hZGRyZXNzIjoidXNlckB0ZXN0LmNvbSIsImZ1bGxfbmFtZSI6Ikdsb2JhbCBVc2VyIiwidXNlcl90eXBlIjoiQURNSU4iLCJ1c2VyX2lkIjoiLTMiLCJzY29wZSI6WyJNRVRBREFUQSIsIlVTRVIiXSwiYXRpIjoiOTMzOGIyYjItYjZiZS00NjZjLWJkYmEtNDZlODJmOTMzNmUzIiwiZXhwIjoxNjIzMDk2ODM5LCJqdGkiOiIzXzAxRjdNMVFaUlZHOUE1MTBWM0M1NFgwRjRXIn0.Fy0Hmq9cqFK7_amumxhRvkWSjK_YCEXxhsa2WVvn0HdF3qewWGjdwsGc1fjwHHTxH2EIX96crR15Jacy3PVEvMewSZtFEG3idto1sormW1baR1BI2gPvHNIuBeljAyV1V9GNAUipvILBmp6AK5-RKOQlG9ICAXFWjh3gtXOziqaGrkfeIQxmdRRnuGXSHuJB9q2uebZOMTF60DksntRhU8374rVNmfuAQ_3PiwXkZWZaG3Q1qhXF6DZtNzTki0Ua2Kk9FLiC3ivW6ZJAG0bipOTYKrgs-hWXYLDqWyr5oJfCJJ7vbgpcFpOhgAwGbe3UA5G3fWT_2PRjNy6rKyiVyA",
  "expires_in":1,
  "scope":"METADATA USER",
  "iss":"broadleaf-authentication",
  "aud":["broadleaf-authentication","oauth2-resource"],
  "max":1623140019,
  "jti":"9338b2b2-b6be-466c-bdba-46e82f9336e3"
}

Using a refresh token

To use a refresh token, the grant_type should be set to refresh_token and the refresh_token parameter set to the encoded refresh token string you want to use. Note, there is no code parameter in this call, as you are instead exchanging a refresh token for a new token pair. The new refresh token may then be later used to request yet another access token, chaining together as many calls as needed.

Sample

Response

{
  "access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnRfaWQiOiI1REYxMzYzMDU5Njc1MTYxQTg1RjU3NkQiLCJhcHBsaWNhdGlvbl9hY2Nlc3MiOnRydWUsImFkbWluX3VzZXJfaWQiOiItMyIsIm1heCI6MTYyMzE0MDU3NCwidXNlcl9uYW1lIjoidXNlckB0ZXN0LmNvbSIsImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsInRlbmFudF9hY2Nlc3MiOnRydWUsImF1dGhvcml0aWVzIjpbIk1FVEFEQVRBIiwiVVNFUiJdLCJjbGllbnRfaWQiOiJhZG1pbiIsImF1ZCI6WyJvYXV0aDItcmVzb3VyY2UiLCJicm9hZGxlYWYtYXV0aGVudGljYXRpb24iXSwiZW1haWxfYWRkcmVzcyI6InVzZXJAdGVzdC5jb20iLCJmdWxsX25hbWUiOiJHbG9iYWwgVXNlciIsInVzZXJfdHlwZSI6IkFETUlOIiwidXNlcl9pZCI6Ii0zIiwic2NvcGUiOlsiTUVUQURBVEEiLCJVU0VSIl0sImV4cCI6MTYyMzA5NzM3NiwianRpIjoiNGFkN2FiMTQtZjY1ZC00ZGE2LTljYTctZWZiZDI0YmZkNDgxIn0.ctTxl7o0vTKs7GQ1PVexmOs2ddF092f8Kb7xST9BEomnVCFyZ53WbdZwSPCBHHgprxXR-tI4IPNVjHyOsxbDJFxuB7_NnWyhbIbM7QmhF8s4VEDvFM47_gYLyBqodfDKCZ5pOYFpBPf0_h21iavy_WF15-jInhlq3PLzwtENcSf6CvQv4hQ4ubCNsBBQJIFbO20FvEASHNirwFTcZKi1Cg-vt7BIOPI5ioLMmPI8nR9g4dusPJ04ALmm67c0VxNsv6uwHBXyyxwkARiJ87gi0Xe7xqApkEP3iXQpSVH3NDvvCL0I3qDth4nC4oLtbw-L5_Ca5-vPbudfEdak19SdZA",
  "token_type":"bearer",
  "refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnRfaWQiOiI1REYxMzYzMDU5Njc1MTYxQTg1RjU3NkQiLCJhcHBsaWNhdGlvbl9hY2Nlc3MiOnRydWUsImFkbWluX3VzZXJfaWQiOiItMyIsIm1heCI6MTYyMzE0MDU3NCwidXNlcl9uYW1lIjoidXNlckB0ZXN0LmNvbSIsImlzcyI6ImJyb2FkbGVhZi1hdXRoZW50aWNhdGlvbiIsIm9pZCI6IjRfMDFGN00yOFRDVjFDODAxSjFXQTZGMDBOSlAiLCJ0ZW5hbnRfYWNjZXNzIjp0cnVlLCJhdXRob3JpdGllcyI6WyJNRVRBREFUQSIsIlVTRVIiXSwiY2xpZW50X2lkIjoiYWRtaW4iLCJhdWQiOlsib2F1dGgyLXJlc291cmNlIiwiYnJvYWRsZWFmLWF1dGhlbnRpY2F0aW9uIl0sImVtYWlsX2FkZHJlc3MiOiJ1c2VyQHRlc3QuY29tIiwiZnVsbF9uYW1lIjoiR2xvYmFsIFVzZXIiLCJ1c2VyX3R5cGUiOiJBRE1JTiIsInVzZXJfaWQiOiItMyIsInNjb3BlIjpbIk1FVEFEQVRBIiwiVVNFUiJdLCJhdGkiOiI0YWQ3YWIxNC1mNjVkLTRkYTYtOWNhNy1lZmJkMjRiZmQ0ODEiLCJleHAiOjE2MjMwOTczOTQsImp0aSI6IjRfMDFGN00yOFhGVEpROEoxRFo1VEpITTEyWEMifQ.lXXpDpKNM9c7P2KsXVR7hl-Gr2RoFiUts4e99w6Dcxnf4EXw1CXkboToEsSdhlyuDw0FtmbTG-93kWhd9L4fO2EAQDhiBVrAjTrB0_1gW6OEGRRERjY9D2L6puj2Jqm2lCAo4ZbtMKpXh0SDnQX288_2-i7ykIYXwu76mr3OqMKDk3OJk5eYeGQXhK13gOiHUaLbq1XcKk06zPaCBfZWP4eh3mC-IIOjKUSfuuSgVTE6x4XEZCsN5lrktxtC5xtgc3ioiBjtPreKpMUZJMzOjAgvT2Dc5x6MVlMA0GjU9UeEo7UH_ObSeTH2Mw1fC4QlL0d2WUHkjkEF5IdXk2PA-w",
  "expires_in":1,
  "scope":"METADATA USER",
  "iss":"broadleaf-authentication",
  "aud":["broadleaf-authentication","oauth2-resource"],
  "max":1623140574,
  "jti":"4ad7ab14-f65d-4da6-9ca7-efbd24bfd481"
}

Rotation interval

A configurable window of time is defined in which a single refresh token may be used multiple times to retrieve a new access token. This window should be short to prevent exploitation. The interval’s primary purpose is to provide some buffer to cover system irregularities, such as mobile client network latency or disruption. In such cases, the application client may need to retry rotation with the same refresh token, and this interval provides some room for that without requiring the user to re-authenticate.

The interval is configured with the broadleaf.auth.token.refresh-token-rotation-interval in millis.

Security Events

If a refresh token is attempted to be re-used outside of the rotation interval, the system determines this to be a security violation and will immediately log the event and delete the entire line of refresh tokens originating from the user’s first access token retrieval. At this point, the legitimate client will not be able to use a refresh token it may be holding from that line and will be forced to re-authenticate if not holding an active session with the auth service.

Enabling refresh token rotation

Tip
In 2.0, refresh tokens are always rotated.

There are several requirements to use refresh token rotation in the 1.x line:

  • The grantTypes list for the targeted authorized client must include the refresh_token grant type. This value is stored in the database in the BLC_CLIENT_GRANT_TYPES table. See the com.broadleafcommerce.auth.server.provider.jpa.domain.JpaAuthorizedClient class for more information.

  • The property broadleaf.auth.token.support-refresh-token-rotation must be set to true in your application property file.

  • The scopes requested during both auth code and token acquisition API calls must include the OFFLINE_ACCESS scope.

Further configuration

See the com.broadleafcommerce.auth.token.TokenProperties for more information on configurable parameters. Also, note the refresh token timeout related settings available as part of the com.broadleafcommerce.auth.server.provider.jpa.domain.JpaAuthorizedClient domain.