Skip to content

Instantly share code, notes, and snippets.

@bdpiprava
Last active October 31, 2018 07:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdpiprava/c069d445c1abc8b984de670efbe6eff2 to your computer and use it in GitHub Desktop.
Save bdpiprava/c069d445c1abc8b984de670efbe6eff2 to your computer and use it in GitHub Desktop.
API token
# This is api tokens doc
@bdpiprava
Copy link
Author

bdpiprava commented Oct 24, 2018

Access Token for GOCD APIs

Currently, API clients send username and password with every API request for authentication. We want to provide an alternative to this authentication method in the form of the access token.

An access token will be used for both authentication and authorization of the client.

Stateless

This type of token will have all the information self-contained and state of this kind of tokens are not stored on server.

Here is the structure of the stateless token

      Body:
      {
        "claims": ["operate:Pipeline1", "view:Pipeline2"]
      }

We looked at stateless tokens for our use cases and found that they are not suitable for various reasons.

  • Invalidate tokens
  • Update existing token
  • Delete previously created tokens

Above all points makes it clear that stateless token is not suitable for auditing as well. Once you generate the token it will be valid till it expires.

Stateful tokens

GoCD server will keep track of tokens in db, information like user associated with the token, claims of the token and expiry.

We have two approaches to generate the stateful token -

  1. Simple access token
  2. Token with explicit granular authorization

Simple access token

This is simple to implement as it inherits all the access from the associated user. API call made with this kind of token will resolve the user and sets it as current user in GoCD this way existing authorization works OOTB.

     Method : POST
     URL    : /go/api/tokens/generate

     Header:
     'Authorization' : 'Basic b64(username:password)' 

Token with explicit granular authorization

The token self-contains the granular access over resources. A user can give a subset of his access to the generated token.

     Method : POST
     URL    : /go/api/tokens/generate

     Header:
     'Authorization' : 'Basic b64(username:password)'

     Body:
     {
       "claims": ["operate:Pipeline1", "view:Pipeline2"]
     }

If we take this road it will be little time consuming since we don't have infrastucture supporting granular access.

It answers following questions -

  • Do we keep track of generated tokens?
  • Do we allow user to invalidate or delete existing token?
  • Do we allow user to increase or decrease access control associated with existing token?
  • What happens when the authorization for user who created the token changes? e.g. UserA generated a token with all permission for PipelineB, later UserA lost his access to PipelineB, what should the token show on UI? should it show some redacted accesses?

Ignore following stuff


Token generation through API

  1. Client sends an request to an endpoint to acquire access token, along with the authentication credentials it also sends bunch of authorization that it would like to have for this token, e.g. Permission to operate on Pipeline1:

      Method : POST
      URL    : /go/api/tokens/generate
    
      Header:
      'Authorization' : 'Basic b64(username:password)'
    
      Body:
      {
        "claims": ["operate:Pipeline1", "view:Pipeline2"]
      }
    
  2. Server will authenticate the client using credentials and read authorization claims and if that user is allowed to have requested authorization then a token will be generated and returned to the client.

    {
      "token": "basdbyu23123b.2jb3bsd.21bsdasd"
    }
    
  3. In subsequent API requests, client now can send acquired token instead of user credentials

    Method : POST
    URL    : /pipelines/pipeline1/trigger
    
    Header
    Authorization: Bearer basdbyu23123b.2jb3bsd.21bsdasd
    
    

Advantages of token over tradiotional authentication:

  1. Fine grained access control: Token can be authorized to access only particular operations
  2. Improved security: Using token instead of actual credentials
  3. Token are stateless: Session created using username:password will become invalid after GoCD server restarts or on security config change but since tokens are stateless no re-authentication will be required

Disadvantages:

Json Web Token

Json web token has following form:

Header
{
“alg” : “HS256",
“typ” : “JWT”
}
.
{
“loggedInAs” : “admin”,
“iat” : 1422779638
}
.
HMAC-SHA256(
encodeBase64Url(header) + ‘.’ +
encodeBase64Url(payload),
secret
)'

asdasdasd.sdasdasdasd.asdasdasd

Types of token to consider

  • Token with expiry: Token with expiration
  • Long lived token: Token that never expires

All token can have authorization on it:

  • Basic authorization

    • Admin, view and operate
  • Advanced authorization

    • Entity level authorization: This will allow admins to control what access token can do. This will require to provide access while creating a token e.g.
    1. read-only token
    2. can be used to only create elastic profiles
    

User scenarios:

  1. Generate a token through api call

A token generated here will have authorization included in token itself and that makes it stateless. As long as token is with user and is not expired user can use it to make subsequent api calls

Here, token is generated by the user, for auditing purpose it should log all actions against username of authenticated user.

  • Client authenticates using following request

    POST /go/api/token
    
    Headers
    Basic username:password
    
    Body
    {
      Claims: [operate:Pipeline1", "view:Pipeline2"]
    }
    
  • Client receives token on successful authentication:

    {
      "token": "basdbyu23123b.2jb3bsd.21bsdasd"
    }
    
  • Client then will make use of acquired token with every subsequent request

    POST /pipelines/pipeline1/trigger
    Header
    Authorization: token basdbyu23123b.2jb3bsd.21bsdasd
    
    

We are considering following alternatives for generating token:

  1. JSON Web Token (JWT)
  2. Paseto

Questions:

  1. Get token using API with username:password

    POST /api/tokens/generate
    GET  /api/tokens
    GET  /api/tokens/id
    DEL  /api/tokens/id
    PUT  /api/tokens/id
    
  2. UI generated token

    • Do we keep track of generated tokens?
    • Do we allow user to invalidate existing token?
    • Do we allow user to increase or decrease access control associated with existing token?
    • What happens when the authorization for user who created the token changes? e.g. UserA generated a token with all permission for PipelineB, later UserA lost his access to PipelineB, what should the token show on UI? should it show some redacted accesses?

userA => asdiajdisjdijasd => all:PipelineB 1 Jan

Api(token) -> Authentication(token) -> check for validity ->

controller(is adming, isgroupadmin) -> services -> return

1
-> ajdasdiajsd
-> {
access: [“pipeline:operate”, “pipeline:view”]
access:

{
  pipeline:
    {
      name: pieplin1,
      permission: 'view'
    }
  }
}

/go/api/pipelines/pipelin1/schedule

{
claims:
}

@bdpiprava
Copy link
Author

bdpiprava commented Oct 29, 2018

Get all tokens

Request

curl -X GET 'http://localhost:8153/go/api/tokens' \
      -u 'admin:badger' \
      -H 'Accept: application/vnd.go.cd.v1+json'

Response

{
  "_links": {
    "doc": {
      "href": "https://api.gocd.org/current/#api-access-token"
    },
    "find": {
      "href": "http://test.host/go/api/tokens/:token_id"
    },
    "self": {
      "href": "http://test.host/go/api/tokens"
    }
  },
  "_embedded": {
    "tokens": [
      {
        "_links": {
          "doc": {
            "href": "https://api.gocd.org/current/#api-access-token"
          },
          "find": {
            "href": "http://test.host/go/api/tokens/:token_id"
          },
          "self": {
            "href": "http://test.host/go/api/tokens/PersonToken"
          }
        },
        "description": "Personal Token",
        "expires_at": 987654321,
        "name": "PersonToken"
      },
      {
        "_links": {
          "doc": {
            "href": "https://api.gocd.org/current/#api-access-token"
          },
          "find": {
            "href": "http://test.host/go/api/tokens/:token_id"
          },
          "self": {
            "href": "http://test.host/go/api/tokens/WorkToken"
          }
        },
        "description": "Token for work only",
        "expires_at": 876543219,
        "name": "WorkToken"
      }
    ]
  }
}
Create a token

Request

curl -X POST 'http://localhost:8153/go/api/tokens/generate' \
      -u 'admin:badger' \
      -H 'Accept: application/vnd.go.cd.v1+json' \
      -H 'Content-Type: application/json' \
      -d '{
      	"name": "Personal", 
      	"expires_in_hours": "4"
      }'

Response

83D1BDC521E44CC9A8905EBF9459A75B
Get a token

Request

curl -X GET 'http://localhost:8153/go/api/tokens/PersonToken' \
      -u 'admin:badger' \
      -H 'Accept: application/vnd.go.cd.v1+json'

Response

83D1BDC521E44CC9A8905EBF9459A75B
Get a token info

Request

curl -X GET 'http://localhost:8153/go/api/tokens/PersonToken/info' \
      -u 'admin:badger' \
      -H 'Accept: application/vnd.go.cd.v1+json'

Response

{
  "_links": {
    "self": {
      "href": "http://test.host/go/api/tokens/PersonToken"
    },
    "doc": {
      "href": "https://api.gocd.org/current/#api-access-token"
    },
    "find": {
      "href": "http://test.host/go/api/tokens/:token_id"
    }
  },
  "name": "PersonToken",
  "description": "Personal Token",
  "expires_at": 987654321
}
Delete a token
curl -X DELETE 'http://localhost:8153/go/api/tokens/PersonToken' \
      -u 'admin:badger' \
      -H 'Accept: application/vnd.go.cd.v1+json'

Response

{
	"message": "Token successfully deleted."
}

@arvindsv
Copy link

arvindsv commented Oct 30, 2018

  • If they have an existing token, can they use that token, instead of basic auth, to create a token? No, for security reasons? Explicitly allowed/disallowed?

  • You were saying super admins can get token info from all users. So, the token info call's response should include something like owner?

  • expires_in_hours seems imprecise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment