Search code examples
loopbackjsopenapi

Loopback 4 OpenAPI connector: Specify Authorization header value per request


I have set up an OpenAPI connector in Loopback 4 as described here and for unauthorized requests, it is working well; I managed to create the respective datasource, service and controller. My service is similar to the GeocoderProvider example, but, let's say, with the following service interface.

export interface MyExternalService {
  search_stuff(params: {query?: string}): Promise<MyExternalServiceResponse>;
}

export interface MyExternalServiceResponse {
  text: string;
}

From my controller, I invoke it like this, where this.myExternalService is the injected service (kind of unrelated, but can Loopback also implicitly parse a JSON response from an external API datasource?):

  @get('/search')
  async searchStuff(@param.query.string('query') query: string): Promise<void> {
    return JSON.parse(
      (await this.myExternalService.search_stuff({query})).text,
    );
  }

Now, the external endpoint corresponding to myExternalService.search_stuff needs an Authorization: Bearer <token> header, where the token is sent to Loopback by the client, i.e. it's not a static API key or so. Assuming I added @param.query.string('token') token: string to the parameter list of my searchStuff controller method, how can I forward that token to the OpenAPI connector? This is the relevant part of the underlying OpenAPI YAML definition file:

paths:
  /search:
    get:
      security:
        - Authorization: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResults'
      operationId: search-stuff
components:
  securitySchemes:
    Authorization:
      type: http
      scheme: Bearer

Solution

  • I am now using the underlying execute function of the OpenAPI connector and manually intercept the request (the object that is passed to requestInterceptor is later passed directly to the http module by Swagger):

        return JSON.parse(
          (
            await this.myExternalService.execute(
              'search_stuff',
              {query},
              {
                requestInterceptor: (req: {headers: {Authorization: string}}) => {
                  req.headers.Authorization = 'Bearer ' + token;
                  return req;
                },
              },
            )
          ).text,
        );
    

    I also added the following method to the MyExternalService interface, inspired by the connector's actual execute function:

      execute(
        operationId: string,
        parameters: object,
        options: object,
      ): Promise<MyExternalServiceResponse>;
    

    Some things I found:

    • Loopback internally uses the swagger-client module to do OpenAPI-based requests.
    • Specifically the securities option of Swagger's execute function expects a Security Definitions Object. There are some quirks with actually passing it to Swagger as well.
    • Internally, Swagger builds the final HTTP request that is sent out here in its source code. There, the securities key is mentioned, yet is is never actually used for the request. This means that manually specifying it in the third parameter of this.myExternalService.execute will change nothing.

    I'll not accept this answer yet and I'm looking forward to finding a more Loopback-like approach.