42CRUNCH BLOG


Creating High Quality OAS Definitions with Springfox – Part 1: Security Definitions


Spring Boot is a popular framework to build applications and APIs. Leveraging the Springfox project and code annotations, developers can generate OAS files with a high 42Crunch Security Audit score.

What is the 42Crunch Security Audit?

The 42Crunch Security Audit is one of 3 services from the 42Crunch API Security Platform: it consumes OpenAPI (Swagger) files and analyzes them along two axes: security and data.

  • At the security level, the audit service looks at the transport used (is it using TLS?), the authentication method (Basic, API keys, OAuth) and in the case of OAuth, the grant type used and scopes. Each issue is given a negative score: the value of the score depends on the data and operation sensitivity. For example, the score is more severely affected if the data is marked as highly sensitive and the operation is DELETE than if the data is insensitive and the operation is a GET.
  • At the data level, the audit service analyzes how headers, path parameters, query parameters and body payloads are specified. In particular, it detects whether strings have patterns and boundaries, numbers have boundaries and in general, if schemas are available for all responses types. The goal is to ensure that all traffic inbound and outbound is thoroughly defined to prevent unwanted traffic from going through our API firewall.

Producing a High Quality OpenAPI File

Our customers use two approaches:

  • API design first, where specialized editors like SwaggerHub or Stoplight are used to create the OpenAPI file.
  • Code first, where the OpenAPI is produced via code annotations. Spring Boot is a widely used framework and it comes with first class support for OAS generation via the Springfox framework. The framework is capable of leveraging Swagger annotations.

In this document, we focus on the core annotations that impact the 42Crunch audit score and therefore allow us to generate a powerful allowlist to protect your APIs.

This is not a beginners guide to Springfox: if you’ve never used Springfox before, please start by following this tutorial.

OAS Security Definitions

Over 30% of the audit score is based on security definitions and how they are applied. Security definitions define how the API is secured: basicauth, api key and OAuth2 are the three authentication types supported by the specification at this time.

We will explain how to add an API key definition and then an OAuth2 definition, which are the most commonly used ones.

Using API keys

Our goal is to add the following security definition to our OAS document: in this case, we have an API key which is passed in a header called x-access-token.

 "securityDefinitions": {
        "access-token": {
            "type": "apiKey",
            "in": "header",
            "name": "x-access-token",
            "description": "Most operations need to user token retrieved calling /api/login"
        }
    }

We first need to add a securityScheme to the API Docket definition, as follows:

 @Bean
public Docket apiDocket() {
   return new Docket(DocumentationType.SWAGGER_2)
	       .select()
	       .apis(RequestHandlerSelectors.basePackage("com.xliic"))	
	       .paths(PathSelectors.any())
	       .build()
	       .apiInfo(getApiInfo())
	       .produces(DEFAULT_PRODUCES_AND_CONSUMES)
	       .consumes(DEFAULT_PRODUCES_AND_CONSUMES)
	       .useDefaultResponseMessages(false)
	       .securitySchemes(securitySchemes()))
	       .enableUrlTemplating(false)
	       .host("apis.xliic.com")
	       .protocols(Stream.of("https").collect(toSet()));
}

The security scheme operation compiles the list of security definitions that can be used in the API. Let’s start by adding a simple API key definition. We first declare the API key scheme:


private ApiKey apikeyScheme() {
	    return new ApiKey("access-token", "x-access-token", "header");
	}

We then add this scheme to a list of schemes. The securitySchemes on the Docket configuration expects a list of security schemes, which you can build as follows:


private ArrayList securitySchemes () {
    ArrayList secList = Lists.newArrayList();
    secList.add(apikeyScheme());		
    return secList;
}

The resulting OpenAPI definition looks like this:

"securityDefinitions": {
    "access-token": {
      "type": "apiKey",
      "name": "x-access-token",
      "in": "header"
    }
  },

Using OAuth as authentication scheme

If our API is using an access token generated through OAuth, we can describe it using an OAuth scheme object.

private OAuth oauthScheme() {
     GrantType grantType = 
	    new AuthorizationCodeGrant( 
	    	new TokenRequestEndpoint(Constants.OAUTH_SERVER + "/authorize", "CLIENT_ID", "CLIENT_SECRET"), 
	    	new TokenEndpoint(Constants.OAUTH_SERVER + "/token", "oauthtoken"));
	    OAuth oauth = new OAuth ("oauth", Arrays.asList(scopes()), Arrays.asList(grantType));
     return oauth;
} 

In this function, we declare the OAuth grant type (here AuthorizationCodeGrant) including the authorization and token endpoints. If scopes are associated, they are defined in an array.

private AuthorizationScope[] scopes() {
	    AuthorizationScope[] scopes = { 
	      new AuthorizationScope("read", "for read operations"), 
	      new AuthorizationScope("write", "for write operations"), 
	      new AuthorizationScope("delete", "for delete operations") };
	    return scopes;
	}

Note that the mandatory CLIENT_ID, CLIENT_SECRET, and OAuth token information are used as part of the Swagger UI rendering (they are not used in the OpenAPI file itself).

The corresponding definition within the OpenAPI definition is as follows:


"securityDefinitions": {
    "oauth": {
      "type": "oauth2",
      "authorizationUrl": "https://oauth.acme.com/authorize",
      "tokenUrl": "https://oauth.acme.com/token",
      "flow": "accessCode",
      "scopes": {
        "read": "for read operations",
        "write": "for write operations",
        "delete": "for delete operations"
      }
    }
  }

Should you want to use both the APIKey and OAuth security definitions, simply add them both to the declaration, like this:

private ArrayList securitySchemes () {
   // Add the two security definitions to a single list
   ArrayList secList = Lists.newArrayList();
   secList.add(apikeyScheme());
   secList.add(oauthScheme());
		
   return secList;
}

Applying Security

Once you have defined the security schemes you will have for your service (API), we can now define how to apply them over it. In our example in Part 1, we defined both schemes: API Key and OAuth, and according to the OAS specification, we can inject the schemas according to our needs, we can let the API with a global security schema, or we can have it defined using a security granularity by resource. 

Applying security at API level

In order to define the global authentication scheme, you need to add the respective authorizations annotation at the controller level, as the following example:


@RestController
@RequestMapping("/v2")
@Api(value = "Set of endpoints for Creating, Retrieving, Updating and Deleting of Persons.", authorizations = {
@Authorization(value = "access-token") })
public class PersonController {

This code snippet sets the global security scheme for this API to be  access-token.

Applying security at operation level

You may need to define a security scheme at the resource level, in other words, you can say: 

  • API MyAPI
    • /customers
      • GET : Use APIKey
      • POST: Use OAuth2

To achieve this, you need to add the authorizations annotation at the API operation level, like this: 


@RequestMapping(method = RequestMethod.GET, path = "/person/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(nickname = "getPersonByID", value = "Retrieve Person by id", notes = "Returns a specific person by their identifier. 404 if does not exist.", response = Person.class,
  authorizations = {
     @Authorization(value = "access-token") })

Similarly, you can specify OAuth2 as the required security scheme for another operation, for example the DELETE operation. 


@RequestMapping(method = RequestMethod.DELETE, path = "/person/{id}")
@ApiOperation(value = "Delete Person by id", notes = "Deletes a person from the system. 404 if the person's identifier is not found.", 
 authorizations = {
    @Authorization(value="oauth",scopes = {@AuthorizationScope(scope="delete",description = "Delete person")})})

42Crunch Extensions Support

It is possible to add custom OAS extensions via Springfox to your definitions. This can be used to inject 42Crunch specific extensions, as illustrated below:


@RequestMapping(method = RequestMethod.POST, path = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Create a person", notes = "API login ", extensions = {
@Extension(properties = { @ExtensionProperty(name = "x-42c-no-authentication", value = "true") })
})

public Person login(
@RequestParam @Min(10) @Max(50) @Pattern(regexp = Constants.EMAIL_REGEX) String email,
@RequestParam @Min(10) @Max(50) @Pattern(regexp = Constants.PASSWORD_REGEX) String password)

This is the resulting OAS snippet:


"paths":{
 "/login":{
 "post":{
   ...
   "deprecated":false,
   "x-42c-no-authentication":"true"
}

This OAS extension allows you to disable authentication checks from the 42Crunch Security Audit on operations in your API that do not need authentication, for example login or register operations used to authenticate.

Conclusion 

The approach presented in this post will let you generate rich, hardened OpenAPI definitions and remediate issues reported by the 42Crunch Security Audit. Adding annotations also brings the benefit of full synchronization between the code and the API contract, which can then be used to test and protect your APIs using additional 42Crunch platform services.

References:

https://medium.com/future-vision/the-great-authenticators-of-rest-738e81109331