Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X-API-KEY header is not being attached via swagger UI #264

Open
JPStrydom opened this issue Sep 17, 2024 · 7 comments
Open

X-API-KEY header is not being attached via swagger UI #264

JPStrydom opened this issue Sep 17, 2024 · 7 comments
Labels

Comments

@JPStrydom
Copy link

I'm following Feather's API key auth strategy as listed here. My application only has API key auth and no other auth stratagies.

I've set up my API key to be read from the header field x-api-key and when running through Postman, the API key auth works as expected. With a correct x-api-key field in a request header, the request authenticates as expected.

I'm struggling to get the Swagger UI to send the header through, even though it looks like it's configured correctly.

My authentication is set up like this:

import { AuthenticationBaseStrategy, AuthenticationService } from '@feathersjs/authentication';
import { NotAuthenticated } from '@feathersjs/errors';

import { services } from '#src/enums';

class ApiKeyStrategy extends AuthenticationBaseStrategy {
  app;

  constructor(app) {
    super();
    this.app = app;
  }

  async authenticate(authentication) {
    const { apiKey } = authentication;

    const { total } = await this.app.service(services.API_KEYS).find({ query: { apiKey, $limit: 0 } });

    const hasAccess = total === 1;

    if (!hasAccess) {
      throw new NotAuthenticated('Incorrect API Key');
    }

    return { apiKey: true };
  }
}

export const authentication = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('apiKey', new ApiKeyStrategy(app));

  // Not sure if I need to configure Swagger for the authentication here?

  app.use('authentication', authentication);
};

My Swagger instance is configured like this:

import feathersSwagger from 'feathers-swagger';

export const swagger = app => {
  const title = app.get('title');
  const description = app.get('description');
  const version = app.get('version');

  app.configure(
    feathersSwagger({
      prefix: /^(api|commands|subscriptions)\//,
      specs: {
        info: { title, description, version },
        components: { securitySchemes: { ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'x-api-key' } } },
        security: [{ ApiKeyAuth: [] }]
      },
      include: { paths: ['api', 'commands', 'subscriptions'] },
      ui: feathersSwagger.swaggerUI()
    })
  );
};

These are then configured in my app.js file like so:

app.configure(swagger);
app.configure(authentication);  
app.configure(api); // Configure all /api/xxx endpoints
app.configure(commands);  // Configure all /commands/xxx endpoints
app.configure(subscriptions); // Configure all /subscriptions/xxx endpoints

My endpoints are each configures like this:

import { authenticate } from '@feathersjs/authentication';
import { hooks as schemaHooks } from '@feathersjs/schema';
import swagger from 'feathers-swagger';

import { claims, methods, services } from '#src/enums';

import { ApiKeysService, getOptions } from './api-keys.class.js';
import { apiKeysDocs } from './api-keys.schema.js';

const path = services.API_KEYS;
const allowedMethods = [methods.FIND, methods.GET, methods.CREATE, methods.PATCH, methods.REMOVE];
const apiKeyDocs = swagger.createSwaggerServiceOptions({
  schemas: { 
    // ...
  },
  docs: {
    description: 'An API key REST service',
    securities: ['ApiKeyAuth']
  }
});

export const apiKeys = app => {
  app.use(path, new ApiKeysService(getOptions(app)), {
    methods: allowedMethods,
    events: [],
    docs: apiKeysDocs
  });
  app.service(path).hooks({
    // ...
  });
};

In the Swagger UI, I can see the correct authentication UI:
image

But when making requests whilst authentication, the header doesn't appear to be attached to the request:
image

I've tried setting cors in my app.js like so, but the x-api-key request header is still not being set:

app.use(
  cors({
    origin: '*', // Adjust based on your security needs
    allowedHeaders: ['x-api-key', 'Content-Type']
  })
);

Does anyone know what I'm missing? Would really like to be able to test the API via the Swagger UI.

Bonus question: Is there a way I could have my sub-endpoints (/api/, /commands/, /subscriptions/) grouped together in Swagger UI, so that I could collapse them independently? I've tried using tags, but these set all the /api/ endpoints under one tag

@Mairu
Copy link
Collaborator

Mairu commented Sep 18, 2024

Hi @JPStrydom,

The securities array in a service's docs section should contain the secured methods f.e. ['get', 'find', ..., /* or just */ 'all'], not the security definition name(s).

By default all configured security definitions from specs.security will be used, which should be fine for your use case.

Just for completeness, to define the security schemes used, the "overriding" logic of docs.operations could be used.

Reference in the examples:

securities: ['create', 'update', 'patch', 'remove'],
operations: {
find: {
security: [
{ BasicAuth: [] }
]
}
}

@JPStrydom
Copy link
Author

securities: ['create', 'update', 'patch', 'remove'],
operations: {
find: {
security: [
{ BasicAuth: [] }
]
}
}

@Mairu You absolute beauty! That did the trick, thank you very much. Is there a way I can define this globally so that it applies to all requests?

@Mairu
Copy link
Collaborator

Mairu commented Sep 18, 2024

As there is no defaults option for securities the only way currently should be by setting it with defaults.getOperationArgs.

So it should be possible to add that in your feathersSwagger({ call.

  ...
  defaults: {
    getOperationArgs({ service }) {
       return { securities: service.docs.securities || ['all'] };
    }
  }
  ...

@Mairu
Copy link
Collaborator

Mairu commented Sep 18, 2024

Regarding the bonus question, yes the tagging is used for grouping, so you have to use the same tag for things that should "belong" together.

@JPStrydom
Copy link
Author

Thanks so much for all the help @Mairu !

One last question: Is there any way to update how the URL's are encoded by Swagger? Now that my requests are working, it looks like Swagger is encoding some of my queries in an incompatible way. The following query, for example:

{
  "$sort": {
    "id": 1,
    "apiKey": 1
  }
}

Get's encoded to http://localhost:3030/api/api-keys?%24sort=%7B%22id%22%3A1%2C%22apiKey%22%3A1%7D by Swagger, which my schema seems to complain about:

{
  "name": "BadRequest",
  "message": "validation failed",
  "code": 400,
  "className": "bad-request",
  "data": [
    {
      "instancePath": "/$sort",
      "schemaPath": "#/properties/%24sort/type",
      "keyword": "type",
      "params": {
        "type": "object"
      },
      "message": "must be object"
    }
  ]
}

When querying through Postman with http://localhost:3030/api/api-keys?$sort[id]=1&$sort[apiKey]=1 the query works correctly.

Is there any way I can update how Swagger encodes URLs? From my research it seems like adding a requestInterceptor could be a workaround, but I haven't been able to get this working with feathers swagger.

@Mairu
Copy link
Collaborator

Mairu commented Sep 18, 2024

I think the encoding is correct, and the handling of these query parameters should be addressed in feathers instead.

Check https://feathersjs.com/help/faq.html#why-are-queries-with-arrays-failing of how to adjust the query parameters parsing in feathers.

If you still want to adjust the swagger config checkout https://github.com/Mairu/feathersjs-swagger-tests/blob/feathers-v5-koa-typebox/src/openapi.ts to see how to adjust the SwaggerUI configuration with feathers-swagger.

@JPStrydom
Copy link
Author

Mhhhh doesn't seem like that works when using Koa, but I'll dig into how this can be done with Koa. Sure it should also be possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants