API Security

Enabling API Security

Summary

This tutorial will explain how to add security to an API.

Info

Prerequisite Environment Setup

Tip

  • ${PWD} works on Linux, MacOS, and Windows (via Powershell)
  • %cd% works on Windows (via cmd)
  • $(cygpath -m -a "$(pwd)") works on Windows (via Cygwin)

Modify API Definition

Add the following details to your API definition.

# enable SSL (encrypted connections) vis HTTPS
schemes:
  - https
  - http

# add OAuth2 security
securityDefinitions:
  token:
    type: oauth2
    tokenUrl: https://cloudsso.cisco.com/as/token.oauth2
    flow: password
    scopes:
      write:tasks: create and modify tasks
      read:task: read tasks

# enable security for all read APIs

# within the body of each GET operation
          security:
            - token:
                - read:tasks

# within the body of each POST/PUT/PATCH/DELETE operation
          security:
            - token:
                - write:tasks

Filename: task-tracker-api.yaml

# Original source
# https://raw.githubusercontent.com/go-swagger/go-swagger/master/examples/task-tracker/swagger.yml
swagger: "2.0"
info:
    title: Issue Tracker
    description: |
        This application implements a very simple issue tracker.
    version: "1.0.0"

# enable SSL (encrypted connections) vis HTTPS
schemes:
  - https
  - http

# add OAuth2 security
securityDefinitions:
  token:
    type: oauth2
    tokenUrl: https://cloudsso.cisco.com/as/token.oauth2
    flow: password
    scopes:
      write:tasks: create and modify tasks
      read:tasks: read tasks

produces:
  - application/json
consumes:
  - application/json

# API versioning (Major Version)
basePath: /v1

definitions:
    Task:
        title: Task
        description: >
          Task is the main entity in this application. Everything revolves around
          tasks and managing them.
        type: object
        properties:
          id:
            title: The id of the task.
            description: >-
              A unique identifier for the task. These are created in ascending order.
            type: string
            format: uuid
          title:
            title: The title of the task.
            description: |
              The title for a task.
            type: string
          description:
            title: The description of the task.
            description: >
              The task description is a longer, more detailed description of the issue.
            type: string
          severity:
            type: integer
            format: int32
          effort:
            description: the level of effort required to get this task completed
            type: integer
            format: int32
          status:
            title: the status of the issue
            description: |
              the status of the issue
            type: string
          assignedTo:
            $ref: '#/definitions/User'
          reportedBy:
            $ref: '#/definitions/User'

    User:
        title: User
        description: >
          This representation of a user is mainly meant for inclusion in other
          models, or for list views.
        type: object
        properties:
          id:
            title: A unique identifier for a user.
            description: >
              This id is automatically generated on the server when a user is created.
            type: string
            format: uuid
          screenName:
            title: The screen name for the user.
            description: |
              This is used for vanity type urls as well as login credentials.
            type: string

paths:
    /tasks:
        get:
          operationId: listTasks
          summary: Lists the tasks
          description: |
            List tasks.
          security:
            - token:
                - read:tasks
          responses:
            200:
              description: Successful response
              schema:
                title: TaskList
                type: array
                items:
                  $ref: '#/definitions/Task'
        post:
          operationId: createTask
          summary: Creates a 'Task' object.
          description: |
            Allows for creating a task.
          security:
            - token:
                - write:tasks
          parameters:
            - name: body
              in: body
              description: The task to create
              required: true
              schema:
                $ref: '#/definitions/Task'
          responses:
            201:
              description: Task created

    /tasks/{id}:
        parameters:
          - name: id
            description: The id of the task
            in: path
            required: true
            type: string
            format: uuid
        get:
          operationId: viewTask
          summary: Gets the details for a task.
          description: |
            The details view of a task.
          security:
            - token:
                - read:tasks
          responses:
            200:
              description: Task details
              schema:
                $ref: '#/definitions/Task'
        put:
          operationId: updateTask
          summary: Updates the details for a task.
          description: |
            Allows for updating a task.
          security:
            - token:
                - write:tasks
          parameters:
            - name: body
              in: body
              description: The task to update
              required: true
              schema:
                $ref: '#/definitions/Task'
          responses:
            200:
              description: Task details
              schema:
                $ref: '#/definitions/Task'

Java Implementation

Validate API Definition

docker run --rm -v ${PWD}:/local -w /local swaggerapi/swagger-codegen-cli validate \
    -i task-tracker-api.yaml

Generate Server

Filename: codegen_config.json

{
  "artifactId": "tracker",
  "groupId": "com.cisco.tracker",
  "basePackage": "com.cisco.tracker",
  "apiPackage": "com.cisco.tracker.api",
  "configPackage": "com.cisco.tracker.config",
  "modelPackage": "com.cisco.tracker.model",
  "hideGenerationTimestamp": true,
  "dateLibrary": "java8",
  "useBeanValidation": true,
  "licenseUrl": ""
}
docker run --rm -v ${PWD}:/local -w /local swaggerapi/swagger-codegen-cli generate \
    -i task-tracker-api.yaml \
    --lang spring \
    -c codegen_config.json

The Output and Files will be the same as previously generated except that within the file TasksApi.java there will now be additional annotation indicating that Authorization is now required.

    @ApiOperation(value = "Creates a 'Task' object.", notes = "Allows for creating a task. ", response = Void.class, authorizations = {
        @Authorization(value = "token", scopes = {
            @AuthorizationScope(scope = "write:tasks", description = "create and modify tasks")
            })
    }, tags={  })
    @ApiResponses(value = {
        @ApiResponse(code = 201, message = "Task created") })
    @RequestMapping(value = "/tasks",
        produces = { "application/json" },
        consumes = { "application/json" },
        method = RequestMethod.POST)

    @ApiOperation(value = "Lists the tasks", notes = "List tasks. ", response = Task.class, responseContainer = "List", authorizations = {
        @Authorization(value = "token", scopes = {
            @AuthorizationScope(scope = "read:tasks", description = "read tasks")
            })
    }, tags={  })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "Successful response", response = Task.class, responseContainer = "List") })
    @RequestMapping(value = "/tasks",
        produces = { "application/json" },
        consumes = { "application/json" },
        method = RequestMethod.GET)

Generate Client

Filename: codegen_config.json

{
  "artifactId": "tracker",
  "groupId": "com.cisco.tracker",
  "basePackage": "com.cisco.tracker",
  "apiPackage": "com.cisco.tracker.api",
  "configPackage": "com.cisco.tracker.config",
  "modelPackage": "com.cisco.tracker.model",
  "hideGenerationTimestamp": true,
  "dateLibrary": "java8",
  "useBeanValidation": true,
  "licenseUrl": ""
}
docker run --rm -v ${PWD}:/local -w /local swaggerapi/swagger-codegen-cli generate \
    -i task-tracker-api.yaml \
    --lang java \
    -c codegen_config.json

The Output and Files will be the same as previously generated except that within the file ApiClient.java there will now be configuration for the authentications.

    public ApiClient() {
        httpClient = new OkHttpClient();


        verifyingSsl = true;

        json = new JSON();

        // Set default User-Agent.
        setUserAgent("Swagger-Codegen/1.0.0/java");

        // Setup authentications (key: authentication name, value: authentication).
        authentications = new HashMap<String, Authentication>();
        authentications.put("token", new OAuth());
        // Prevent the authentications from being modified.
        authentications = Collections.unmodifiableMap(authentications);
    }

Golang Implementation

Update the GOPATH environment variable within the container by appending the base directory; e.g. /tracker. Mount the project root directory to base directory /tracker/src/<import path> where the <import path> is how you import a golang package.

Validate API Definition

docker run --rm -v ${PWD}:/local -w /local quay.io/goswagger/swagger validate task-tracker-api.yaml

Generate Server

docker run --rm \
    -e GOPATH='/go:/tracker' \
    -v ${PWD}:/tracker/src/gitscm.cisco.com/myorg/go-trackerclient \
    -w /tracker/src/gitscm.cisco.com/myorg/go-trackerclient \
    quay.io/goswagger/swagger generate server \
        -A TrackerApi \
        -f task-tracker-api.yaml \
        --principal models.User

The Output and Files will be the same as previously generated except that within the file configure_tracker.go there will now be additional configuration indicating that token authenication is now required.

  api.TokenAuth = func(token string, scopes []string) (*models.User, error) {
    return nil, errors.NotImplemented("oauth2 bearer auth (token) has not yet been implemented")
  }

Generate Client

docker run --rm \
    -e GOPATH='/go:/tracker' \
    -v ${PWD}:/tracker/src/gitscm.cisco.com/myorg/go-trackerclient \
    -w /tracker/src/gitscm.cisco.com/myorg/go-trackerclient \
    quay.io/goswagger/swagger generate client \
        -A TrackerApi \
        -f task-tracker-api.yaml \
        --principal models.User

The Output and Files will be the same as previously generated except that within the file operations_client.go there will now be additional parameter for each operation indicating that token authenication is now required.

func (a *Client) CreateTask(params *CreateTaskParams, authInfo runtime.ClientAuthInfoWriter) (*CreateTaskCreated, error)

Additional information

If the security configuration for each operation is identical, then the security can be defined globally within the API definition.

# Adds security for all API operations
security:
  - token: []

If an operation does not require security, then security must be defined for each individual operation and the operation(s) that should be unprotected (requires no authentication) should not have security defined within the operation.

paths:
    /tasks:
        get:
          operationId: listTasks
          summary: Lists the tasks
          description: |
            List tasks.
          responses:
            200:
              description: Successful response
              schema:
                title: TaskList
                type: array
                items:
                  $ref: '#/definitions/Task'