3
\$\begingroup\$

My team and I are trying to implement a secure API and would appreciate some feedback on the following:

  • If there are areas of improvement in the current code to improve security

  • Recommendations on other secure API implementations, if any, that would fit our requirements better (see below)

  • If there are any major loopholes in security based on this module.

Here are some requirements, considerations, and caveats:

  • The API clients/consumers are other web apps from our same company (internal yet public facing apps on a different sub-domain)

  • We'd prefer not to go the OAuth2 route. We'd like this to be a programmatic interface using key-based authentication. Basically, it will be server to server authentication.

  • API is already https

We'd like this to be modular/flexible enough to use across different internal apps

Please read code comments. We've gotten inspiration from multiple sources, especially the implementation of JWT in a slightly different manner.

# Inspiration from:
#   - https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth
#   - https://codeburst.io/jwt-to-authenticate-servers-apis-c6e179aa8c4e
# Typically, JWT's are used to provide consumer an access token after successful login.
# Considering our use case, the consumer (another internal app) will initiate the JWT
# Server will verify authenticity of request if it is able to decode JWT
# We've also set specific headers that internal apps would need to provide

module SecureAPI
  # Generates a secure payload for outgoing requests
  def self.secure_payload(options = {}, method = "get")
    # Get or Post request
    param_type = method == "get" ? "query" : "body"
    iat = Time.now.to_i

    # Reserved and Private claims
    token_payload = {
      # The "iat" (issued at) claim identifies the time at which the JWT was issued.
      iat: iat,
      # Expiration
      exp: iat + 60,
      # Private claim
      client_id: options[:client_id]
    }

    encoded_token = encode_token(token_payload, options[:shared_secret])

    payload = {
      :headers => {
        "Authorization" => "Bearer" + ' ' + encoded_token,
        "JWT-Consumer" => options[:client_id]
      },
      param_type.to_sym => {
         client_token: options[:client_token]
      }
    }
    payload
  end

  # Verifies incoming requests/JWT verifier
  def self.authenticate_request(request)
    # Client must provide these headers
    jwt_token = request.headers['Authorization'].split(' ').last

    shared_secret = Rails.application.secrets[request.headers['JWT-Consumer']]

    # Token must be decoded, unexpired, and verified using same hashing algo for successful auth
    # else will return unauthorized response to client
    decode_token(jwt_token, shared_secret)
  end

  def self.encode_token(payload, secret)
    JWT.encode payload, secret, 'HS512'
  end

  def self.decode_token(token, shared_secret)
    JWT.decode token, shared_secret, true, algorithm: 'HS512'
  end
end

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.