Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

cerner/cerner-oauth1a

Repository files navigation

Cerner OAuth 1.0a Consumer and Service Provider Library

Build Status Gem Version AwesomeCode Status

A minimal dependency library for interacting with a Cerner OAuth 1.0a Access Token Service for invoking Cerner OAuth 1.0a protected services or implementing Cerner OAuth 1.0a authentication. Cerner's OAuth 1.0a Access Token Service provides a means for facilitating two-legged (B2B) authentication via a variant of OAuth 1.0a.

Usage

There are two use cases for working with this library: Consumer and Service Provider. The Consumer Use Case is for invoking services protected by Cerner OAuth 1.0a. The Service Provider Use Case is for implementing a Ruby-based service.

Consumer Use Case

require 'cerner/oauth1a'
require 'net/http'

# Setup the AccessTokenAgent with an Access Token Service's URL, a Key and a Secret
agent = Cerner::OAuth1a::AccessTokenAgent.new(
  access_token_url: 'https://oauth-api.cerner.com/oauth/access',
  consumer_key: 'CONSUMER_KEY',
  consumer_secret: 'CONSUMER_SECRET'
)

# Retrieve an AccessToken instance
access_token = agent.retrieve

# Setup the HTTP library to access the protected API you want to invoke
uri = URI('https://authz-demo-api.cerner.com/me')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'

# Invoke the API's HTTP endpoint and use the AccessToken to generate an Authorization header
response = http.request_get(uri.path, Authorization: access_token.authorization_header)

Consumer HMAC-SHA1 Signature Method

The preferred and default signature method is PLAINTEXT, as all communication SHOULD be via TLS. However, if HMAC-SHA1 signatures are necessary, then this can be achieved by constructing AccessTokenAgent as follows:

agent = Cerner::OAuth1a::AccessTokenAgent.new(
  access_token_url: 'https://oauth-api.cerner.com/oauth/access',
  consumer_key: 'CONSUMER_KEY',
  consumer_secret: 'CONSUMER_SECRET',
  signature_method: 'HMAC-SHA1'
)

To use the AccessToken requires additional parameters to be passed when constructing the Authorization header. The HTTP method, the URL being invoked and all request parameters. The request parameters should include all parameters passed in the query string and those passed in the body if the Content-Type of the body is application/x-www-form-urlencoded. See the specification for more details.

Consumer HMAC-SHA1 Signature Method Examples

GET with no request parameters

uri = URI('https://authz-demo-api.cerner.com/me')
# ...
authz_header = access_token.authorization_header(fully_qualified_url: uri)

GET with request parameters in URL

uri = URI('https://authz-demo-api.cerner.com/me?name=value')
# ...
authz_header = access_token.authorization_header(fully_qualified_url: uri)

POST with request parameters (form post)

authz_header = access_token.authorization_header(
  http_method: 'POST'
  fully_qualified_url: 'https://example/path',
  request_params: {
    sort: 'asc',
    field: ['name', 'desc'] # sending the field multiple times
  }
)

PUT with no request parameters (entity body)

authz_header = access_token.authorization_header(
  http_method: 'PUT'
  fully_qualified_url: 'https://example/path'
)

Access Token Reuse

Generally, you'll want to use an Access Token more than once. Access Tokens can be reused, but they do expire, so you'll need to acquire new tokens after one expires. All of the expiration information is contained in the AccessToken class and you can easily determine if a token is expired or about to by using the AccessToken#expired? method. Below is an example of you might implement that:

uri = URI('https://authz-demo-api.cerner.com/me')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'

access_token = agent.retrieve if access_token.expired?

response = http.request_get(uri.path, Authorization: access_token.authorization_header)

Service Provider Use Case

# Acquire Authorization header value from HTTP server's request
authz_header = request['Authorization']

# Parse the header value
access_token = Cerner::OAuth1a::AccessToken.from_authorization_header(authz_header)

# Authenticate the Access Token
# Note: An AccessTokenAgent, configured with a System Account that has been granted privileges
# to Acquire Tokens and Process Tokens.
begin
  results = access_token.authenticate(agent)
rescue OAuthError => e
  # respond with a 401
end

# Use Consumer Key (i.e. the System Account) to do further authorization, as appropriate
system_account_id = access_token.consumer_key

# Optionally, extract additional parameters sent with the token, such as Consumer.Principal
# (xoauth_principal)
consumer_principal = access_token.consumer_principal

Service Provider HMAC-SHA1 Signature Method

The preferred and default signature method is PLAINTEXT, as all communication SHOULD be via TLS. However, if HMAC-SHA1 signatures are necessary, then this can be achieved by passing additional informational to the authenticate method.

begin
  results = access_token.authenticate(
    agent,
    http_method: request.method,
    fully_qualified_url: request.original_url,
    request_params: request.parameters
  )
rescue OAuthError => e
  # respond with a 401
end

Caching

The AccessTokenAgent class provides built-in memory caching. AccessTokens and Keys are cached behind their respective retrieve methods. The caching can be disabled via parameters passed to the constructor. See the class-level documentation for details.

Caching in Rails

When the gem is loaded within a Rails application, it will attach a Railtie for initializing the cache to use an implementation that stores the AccessTokens and Keys within Rails.cache.

References

Installing

This library can be installed using the gem command or added to a Gemfile for use with Bundler.

gem command

$ gem install cerner-oauth1a

Gemfile

gem 'cerner-oauth1a', '~> 2.0'

Building

This project is built using Ruby 2.5+, Rake and Bundler. RSpec is used for unit tests and SimpleCov is utilized for test coverage. RuboCop is used to monitor the lint and style.

Setup

To setup the development workspace, run the following after checkout:

gem install bundler
bundle install

Tests

To run the RSpec tests, run the following:

bin/rspec

Lint

To analyze the project's style and lint, run the following:

bin/rubocop

Bundler Audit

To analyze the project's dependency vulnerabilities, run the following:

bin/bundle audit

Availability

This RubyGem will be available on https://rubygems.org/.

Communication

All questions, bugs, enhancements and pull requests can be submitted here, on GitHub via Issues.

Contributing

See CONTRIBUTING.md

LICENSE

Copyright 2020 Cerner Innovation, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.