Skip to main content

Remote Request Hook

The remote request hook allows witboost to forward all data that is received by an action to a configured URL. For instance, for the Access Request action, this hook allows to forward all the access request data to an external service, through a dedicated endpoint that can handle the request in a specific manner as required by your needs.

tip

You can use the Access Request action in combination with the Remote Request hook to model an external authorization system. Such that users can accept or reject access request on a separate platform outside witboost.

To setup a remote request hook you need to provide:

  • A service URL that can accept a JSON body and a POST method. The action's input data will be forwarded when the action is triggered (e.g. the input data for the access request action is the requester information and requested resource information)
  • The service must update the hook state once the third-party interaction is completed. The URL where to update the hook status is provided by witboost in the original request's body.
  • The witboost's secret key that you generated at installation time, so that your service is authorized to update the hook status on witboost
  • If your external service requires complex authorization schema (e.g. OAuth2), you will need to provide a microservice that acts as a proxy between witboost and the target service. The microservice will be in charge of handling the authorization and forwarding the request coming from the remote request hook.

Adding a Remote Request hook to an Action

If you want to add the remote request hook for an action, you can place the hook under the hooks property in the app-config. Example on how to add this hook to the Access Request\ action:

# in app-config.yaml
actionHandlers:
accessRequest:
# access request configurations
hooks:
- type: remoteRequest
# remote request configurations

Configuring the Remote Request Hook

To configure the target URL where the request must be sent by the remote request hook, along with additional configuration, you can modify the app-config accordingly:

# in app-config.yaml
actionHandlers:
<action name>:
# action configurations
hooks:
- type: remoteRequest
target: http://simple.example.com:8080/endpoint/v1

Under the target property, we configure the external service to be contacted by defining its full URL.

For more complex scenarios, where you need to pass API Tokens, Bearer tokens or even custom headers attached to the request, you can pass additional headers in the dedicated optional section headers:

# in app-config.yaml
actionHandlers:
<action name>:
hooks:
- type: remoteRequest
target: http://simple.example.com:8080/endpoint/v1
headers:
Authorization: ${EXAMPLE_AUTH_HEADER}
# ...or interpolating a value into part of a string,
# Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}
tip

If you need to override the default baseUrl that is given to your external service in the respondToUrl parameter you can use the patchBaseUrl field in this way:

actionHandlers:
<action name>:
hooks:
- type: remoteRequest
target: http://simple.example.com:8080/endpoint/v1
patchBaseUrl: https://my-custom-domain.com/api/actionHandler

patchBaseUrl is the base URL of the action handler API.

This way, the respondToUrl received by your external service will be https://my-custom-domain.com/api/actionHandler/hooks/{id}.

You can leverage this option for cases in which witboost is not directly accessible from other clusters or networks on your infrastructure.

Implementing a compliant service for the Remote Request Hook API

If you need to provide a microservice, as for the example use-case described above, it will need to fulfill the following contract:

1. Receive a POST request at any endpoint of your choice

A request with all the action's data is sent to the target URL with a POST HTTP request. Eventually, this request includes some headers that you can use for authentication purposes. This is configured in the remote request hook's section of the app-config under the headers property.

Upon receiving the POST request, the endpoint should first respond with a 202 HTTP status code (ACCEPTED), postponing all the eventually heavy work in a second moment, asynchronously. This is to prevent that the UI keeps spinning any loading bar or that any request timeout triggers.

diagram

Once you accepted the request you can execute the business logic of your external service. e.g. your external service registers the request into a third-party ticketing system.

This is the Open API specification of the POST endpoint that your service must expose:

Remote Request Hook API Specification
openapi: 3.0.3
info:
title: Remote Request Hook API Specification
description: 'Service responsible to manage requests coming from the Remote Request hook configured in witboost.'
version: '0.1'
tags:
- name: RemoteService
paths:
'/any-path-at-your-microservice':
post:
tags:
- RemoteService
summary: Receive a request from witboost about an ongoing action started by a user, that requires a third-party interaction on your end
parameters:
- in: header
name: X-Request-ID
schema:
type: string
format: uuid
required: true
requestBody:
description: An object containing all the input data received by the action where the remote request hook is attached
content:
application/json:
schema:
$ref: '#/components/schemas/Request'
required: true
responses:
202:
description: Declares that you accepted the remote request and you are asynchronously working on it, or that the request has been registered into an external system and it is waiting for a user interaction
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Response'
- $ref: '#/components/schemas/TraceableResponse'
400:
description: A malformed input has been received
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized sender. For services that implements an authorization mechanism, this is the error code to answer in case of misconfigurations or failed authorization attempts
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Internal server error during the request processing
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Request:
type: object
required:
- respondToUrl
- fields
properties:
respondToUrl:
type: string
description: URL to be contacted to notify that the hook is completed. This represents the fact that the third-party interaction has been performed on your end. e.g. An access request that has been accepted on your dedicated platform
fields:
type: object
description: A free-form object containing all the action's input data coming from witboost
Response:
type: object
required:
- hookInfo
properties:
hookInfo:
type: object
description: A free-form object that will be stored together as the hook's response data

TraceableResponse:
type: object
required:
- hookInfo
properties:
hookInfo:
type: object
description: A free-form object that will be stored as the hook's response data with an optional reserved field name for tracking information
properties:
tracking:
type: object
description: A reserved field for any available tracking information for the users to check the status of their request in the external system
properties:
id:
type: string
description: An optional id of the request on the external system, this is helpful if the external system is known to witboost and can reconstruct the URL that a user should visit. Or when it's not possible to provide a viewable URL for the user to check the status of the request.
url:
type: string
description: An optional URL of the request on the external system, it should link to an external system that can show to the users the status of their requests
additionalProperties: true

Error:
type: object
required:
- error
properties:
error:
type: object
required:
- message
properties:
message:
type: string
description: An informative user-friendly message that informs about any occured error during the remote request handling
additionalProperties: true

2. Update back the hook status on Witboost

When a third-party interaction happened on your end, (e.g. the owner of the resource accepted the request on the ticketing system) the service must update the hook status to signal that the hook is completed and that is not anymore waiting for a status update.

To do so, you can use the respondToUrl request field provided in the original request. Your service must send a PATCH request to the respondToUrl URL as described here.

diagram

warning

Make sure Witboost is reachable by your external service. Check Configuring the remote request hook.

Moreover, this HTTP call must be authorized with a server-to-server JWT token that is generated using of the secrets under backend.auth.keys[i].secret. For more details see below in the dedicated section Authorizing the request from the external microservice towards witboost

note

Why should I update back the hook status on Witboost?

This is needed if, for instance, there is any notification to be delivered to users upon the completion of the hook, e.g. once the hook status is OK, a notification is dispatched to the original requester informing that an access request has been accepted or rejected on your authorization platform.

Authenticating requests from the external microservice towards witboost

For secure interaction between an external microservice and witboost, it's essential to authenticate the requests made by the microservice. This authentication ensures that only authorized services can update the status of hooks in witboost, maintaining the integrity and security of the system. Below are the steps and configurations required for this process:

Generating a Secure Token

To securely interact with witboost, external microservices need to generate a JWT (JSON Web Token) that adheres to specific standards. This token is used to authenticate requests made by the microservice to update the status of hooks in witboost.

  1. Token Requirements:

    • JWT with HS256 Signature: The token must be a JWT that uses an HS256 signature algorithm.
    • Secret Key Usage: Use the raw base64 decoded value of the configured key from witboost as the secret for the JWT.
    • Payload Configuration:
      • sub: This field should be set to "backstage-server" as this is the only value supported currently.
      • exp: Set the expiration time to one hour from the time of token generation, in epoch seconds.
    • Protected Header: Ensure that the JWT encodes the alg header as a protected header. In many JWT libraries, this can be achieved with a method like setProtectedHeader.
  2. Token Generation Across Languages: Given that external callers may be written in languages other than Node.js, it's important to use a JWT library compatible with your programming language that supports these specifications.

  3. Secure Token Handling:

    • Store the JWT securely within your microservice.
    • Avoid exposing the token or the secret key used for its generation.

Implementing Token Generation in Code

Here's an example of how you might generate a token in Node.js using the jose library:

async function getToken() {
const secret = jose.base64url.decode(
'<any witboost secret under backend.auth.keys>',
);
const alg = 'HS256';

const jwt = await new jose.SignJWT({ 'urn:example:claim': true })
.setProtectedHeader({ alg })
.setIssuedAt()
.setIssuer('urn:example:issuer')
.setAudience('urn:example:audience')
.setSubject('backstage-server') // must be backstage-server
.setExpirationTime('1h')
.sign(secret);

console.log(`using jwt: ${jwt}`);
return jwt;
}

Adding Authentication to Requests

Attaching the Token: When your microservice sends a PATCH request to witboost to update the status of a hook, the JWT token must be included in the request header. This must be done as a Bearer token in the Authorization header.

Example:

Authorization: Bearer <Your-Generated-JWT-Token>