The POST/oauth/token implementation

For the CCG flow, the service that wants a token sends a POST request with an URL-encoded body that contains the following fields:

  • client_id: ;This is a unique string identifying the requester.
  • client_secret: This is a secret key that authenticates the requester. It should be a random string generated up-front and registered with the auth service.
  • grant_type: This is the grant type, must be client_credentials.

We'll make a few assumptions to simplify the implementation:

  • We're keeping the list of secrets in a Python mapping
  • client_id is the name of the microservice
  • The secret is generated with binascii.hexlify(os.urandom(16))

The authentication part will just ensure that the secret is valid, then the service will create a token and return it:

    import time
from flask import request, current_app, abort, jsonify from werkzeug.exceptions import HTTPException from flakon import JsonBlueprint from flakon.util import error_handling import jwt home = JsonBlueprint('home', __name__) def _400(desc): exc = HTTPException() exc.code = 400 exc.description = desc return error_handling(exc) _SECRETS = {'strava': 'f0fdeb1f1584fd5431c4250b2e859457'} def is_authorized_app(client_id, client_secret): return compare_digest(_SECRETS.get(client_id), client_secret) @home.route('/oauth/token', methods=['POST']) def create_token(): key = current_app.config['priv_key'] try: data = request.form if data.get('grant_type') != 'client_credentials': return _400('Wrong grant_type') client_id = data.get('client_id') client_secret = data.get('client_secret') aud = data.get('audience', '') if not is_authorized_app(client_id, client_secret): return abort(401) now = int(time.time()) token = {'iss': 'https://tokendealer.example.com', 'aud': aud, 'iat': now, 'exp': now + 3600 * 24} token = jwt.encode(token, key, algorithm='RS512') return {'access_token': token.decode('utf8')} except Exception as e: return _400(str(e))

The create_token() view uses the private key found in the application configuration under the priv_key key.

The ;hmac.compare_digest() function is used to compare the two secrets to avoid a timing attack by a client which would try to guess the client_secret one character at a time. It's equivalent to the "==" operator.
From the documentation: This function uses an approach designed to prevent timing analysis by avoiding content-based short circuiting behavior, making it appropriate for cryptography

This blueprint is all we need with a pair of keys to run a microservice that will take care of generating self-contained JWT tokens for all our microservices that require authentication.

The whole source code of the TokenDealer microservice can be found at https://github.com/Runnerly/tokendealer where you can look at how the two other views are implemented.

The microservice could offer more features around token generation. For instance, the ability to manage scopes and make sure microservice A is not allowed to generate a token that can be used in microservice B ;or managing a whitelist of services that are authorized to ask for some tokens.

But the pattern we've implemented is the basis for an efficient token-based authentication system in a microservice environment, you can develop on your own, and is good enough for our Runnerly app.

In the following diagram, training plans, data service, and races can use JWT tokens to restrict access to their respective endpoints:

JWT access in this diagram means that the service requires a JWT token. Those services may validate the token by calling the TokenDealer. The Flask app in this diagram needs to obtain tokens from the TokenDealer on behalf of its users (link not shown in the diagram).

Now that we have a TokenDealer service that implements CCG, let's see in practice how it can be used by our services the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset