Skip to content

Commit

Permalink
Improve standards for repo (#3)
Browse files Browse the repository at this point in the history
* Update readme

* Use standard libraries for http and json

* Update vendor

* rubocop lint

* Remove linebreak in example hash

* Update changelog

* Remove debugging comments

* readme formatting
  • Loading branch information
WilliamNHarvey authored Apr 19, 2022
1 parent dd7a56b commit 765125c
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
=========

## 0.1.1 - 04/18/2022
* Use Faraday and MultiJson
* Update [README.md]("./README.md")

## 0.1.0 - 04/14/2022
* Unlock omniauth-oauth2 dependency

Expand Down
9 changes: 5 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
PATH
remote: .
specs:
omniauth-doximity (0.1.0)
json
omniauth-doximity (0.1.1)
activesupport
faraday
jwt
multi_json
omniauth-oauth2
openssl

Expand Down Expand Up @@ -34,7 +36,6 @@ GEM
hashie (5.0.0)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
json (2.6.1)
jwt (2.3.0)
minitest (5.15.0)
multi_json (1.15.0)
Expand Down Expand Up @@ -112,7 +113,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
bundler (~> 2.1.4)
bundler (~> 2.3.11)
dox-best-practices
dox-style
omniauth-doximity!
Expand Down
133 changes: 132 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,134 @@
# Omniauth::Doximity

OmniAuth strategy for Doximity
OmniAuth strategy for Doximity.

Sign up for Doximity's API to get your OAuth credentials at: https://www.doximity.com/developers/api_signup

For more details on what tools we have available, read our developer docs: https://www.doximity.com/developers/documentation

## Installation

Add to your `Gemfile`:

```ruby
gem 'omniauth-doximity'
```

Then `bundle install`.

## Usage

Here's an example for adding the middleware to a Rails app in `config/initializers/omniauth.rb`:

```ruby
DOXIMITY_OMNIAUTH_SETUP = lambda do |env|
env['omniauth.strategy'].options[:client_id] = ENV["DOXIMITY_CLIENT_ID"]
env['omniauth.strategy'].options[:client_secret] = ENV["DOXIMITY_CLIENT_SECRET"]
env['omniauth.strategy'].options[:scope] = "openid profile:read:basic profile:read:email"
end

Rails.application.config.middleware.use OmniAuth::Builder do
configure do |config|
config.path_prefix = '/auth'
end
provider :doximity, setup: DOXIMITY_OMNIAUTH_SETUP
end
```

Talk with the Doximity API team about what scopes you need for your application, and make sure to edit your OmniAuth initializer to request them.

Update your `config/routes.rb` to support Doximity OmniAuth callbacks on your session controller:

```ruby
Rails.application.routes.draw do
get "/omniauth/:provider/callback" => "sessions#create"
post "/signout" => "sessions#destroy"
get "/omniauth/failure" => "sessions#failure"
end
```

Then, create a sign-in button that posts to `/auth/doximity`. Use one of the Sign in with Doximity logos, available here: https://www.doximity.com/developers/documentation#logos-for-use-by-third-party-developers

```ruby
<%= link_to "Sign in with Doximity", "/auth/doximity", method: :post do %>
<%= image_tag "https://assets.doxcdn.com/image/upload/v1/apps/doximity/api/api-button-sign-in-with-doximity.png", alt: "Sign in with Doximity button"%>
<% end %>
```
Note that in OmniAuth versions 2 and above, links to sign in should use the POST method. Read more [here](https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284)
In your callback controller, you will have a few resources available to you after the user approves your application and logs in.
```ruby
class SessionsController < ApplicationController
def create
session[:user_uuid] = request.env["omniauth.auth"]["uid"]
redirect_to request.env["omniauth.origin"] || "/", :notice => "Signed in!"
end

def destroy
session.delete(:user_uuid)
redirect_to "/"
end

def failure
redirect_to request.env["omniauth.origin"] || "/", :alert => "Authentication error: #{params[:message].humanize}"
end
end
```

You can also add an `origin` param to your `/auth/doximity` post, which will be provided in the `request.env["omniauth.origin"]` variable after the success or failure callback.

## Configuration

You can configure several options, inside the configuration lambda:

* `[:scope]`: A comma-separated list of permissions you want to request from the user.Caveats:
* The `openid` scope is suggested. Alternatively, if the `openid` scope is not requested `omniauth-doximity` will make an additional request to retrieve information about the signed in user using your other scopes. Your app may be subject to rate limiting depending on your usage.
* Without any scopes, you will still be able to log in the user and retrieve a unique UUIDv4 to distinguish them from other users.

* `[:name]`: The name of the strategy. The default name is `doximity` but it can be changed to any string. The `:provider` part of OmniAuth URLs will also change to `/auth/{{ name }}`.

* `[:client_options][:site]`: Override the Doximity OAuth provider website. You may be provided with a development site to use while setting up your integration, which you would set here.

* `[:pkce]`: A boolean denoting whether to follow the PKCE OAuth spec. Default `true`. Note that if set to false, your OmniAuth credentials hash will not include a `refresh` token. Your OAuth application also may require PKCE to use OmniAuth.

## Auth Hash

Here's an example of an authentication hash available in the callback by accessing `request.env['omniauth.auth']`:

```ruby
{
"provider" => "doximity",
"uid" => "cc485bd2-b25a-4677-b05c-e98febf7789d",
"info" => {
"name" => "Test User",
"given_name" => "Test",
"family_name" => "User",
"primary_email" => "md@doximity.com",
"emails" => ["md@doximity.com"],
"profile_photo_url" => "http://res.cloudinary.com/doximity-development/image/upload/l_text:Helvetica_130_bold:AT,co_rgb:FFFFFF,t_profile_photo_320x320/profile-placeholder-registered-5.jpg",
"credentials" => "Other",
"specialty" => "Optometrist"
},
"credentials" => {
"token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==",
"refresh_token" => "go-40T6xPOzSOd09NTElQ0tGi-BU5hluljET8wa3syzxBqsG5BP0PJW_CsbDhmm49T081jhsIMnP-OQG8McYYPdOENc027K87gGSurOquANzx8qlo4hTJ903LNGpTZ6VcV1Ci0jomvJdH1NsCq5nLxeCy4dBctTZEMA-c3pOVZ0=",
"expires_at" => 1650335410,
"expires" => true,
"access_token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==",
"scope" => "profile:read:email profile:read:basic openid",
"token_type" => "bearer"
}, "extra" => {
"raw_subject_info" => {
"acr" => 2, "at_hash" => "uVfpy56HzI3J_dZR2kyxrQ", "aud" => ["https://auth.doximity.com", "6bd7e37e80fd06819ca13b268adea5fbe57446a9f9e1982f9483813d7272acf1"], "auth_time" => 1650333376, "azp" => "6bd7e37e80fd06819ca13b268adea5fbe57446a9f9e1982f9483813d7272acf1", "credentials" => "Other", "emails" => ["md@doximity.com"], "exp" => 1650335384, "family_name" => "User", "given_name" => "Test", "iat" => 1650333610, "iss" => "https://auth.doximity.com", "name" => "Test User", "primary_email" => "md@doximity.com", "profile_photo_url" => "http://res.cloudinary.com/doximity-development/image/upload/l_text:Helvetica_130_bold:AT,co_rgb:FFFFFF,t_profile_photo_320x320/profile-placeholder-registered-5.jpg", "sid" => "9", "specialty" => "Optometrist", "sub" => "cc485bd2-b25a-4677-b05c-e98febf7789d"
}, "raw_credential_info" => {
"token_type" => "bearer", "scope" => "profile:read:email profile:read:basic openid", "id_token" => "{{JWT omitted for brevity}}", "access_token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==", "refresh_token" => "go-40T6xPOzSOd09NTElQ0tGi-BU5hluljET8wa3syzxBqsG5BP0PJW_CsbDhmm49T081jhsIMnP-OQG8McYYPdOENc027K87gGSurOquANzx8qlo4hTJ903LNGpTZ6VcV1Ci0jomvJdH1NsCq5nLxeCy4dBctTZEMA-c3pOVZ0=", "expires_at" => 1650335410
}
}
}
```

## License

Licensed under Apache-2.0, see [LICENSE.txt](./LICENSE.txt)
26 changes: 26 additions & 0 deletions lib/omniauth-doximity/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Omniauth
module Doximity
class JWKSRequestError < StandardError
MESSAGE = "Failed to request public keys for user info verification"
attr_reader :url
attr_reader :response

def initialize(url, response)
@url = url
@response = response
super(MESSAGE)
end
end

class JWTVerificationError < StandardError
MESSAGE = "Failed to verify user info JWT"
attr_reader :token
attr_reader :error

def initialize(error, token)
@token = token
super(error.message)
end
end
end
end
2 changes: 1 addition & 1 deletion lib/omniauth-doximity/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Omniauth
module Doximity
VERSION = "0.1.0"
VERSION = "0.1.1"
end
end
34 changes: 23 additions & 11 deletions lib/omniauth/strategies/doximity.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
require "omniauth/strategies/oauth2"
require "omniauth-doximity/errors"
require "active_support/core_ext/hash/indifferent_access"
require "uri"
require "rack/utils"
require "jwt"
require "net/http"
require "json"
require "faraday"
require "multi_json"

module OmniAuth
module Strategies
Expand Down Expand Up @@ -32,9 +34,15 @@ class Doximity < OmniAuth::Strategies::OAuth2
info do
prune({
name: raw_subject_info["name"],
given_name: raw_subject_info["given_name"],
middle_name: raw_subject_info["middle_name"],
family_name: raw_subject_info["family_name"],
primary_email: raw_subject_info["primary_email"],
emails: raw_subject_info["emails"],
permissions: raw_subject_info["permissions"],
profile_photo_url: raw_subject_info["profile_photo_url"]
profile_photo_url: raw_subject_info["profile_photo_url"],
credentials: raw_subject_info["credentials"],
specialty: raw_subject_info["specialty"],
permissions: raw_subject_info["permissions"]
})
end

Expand All @@ -47,9 +55,9 @@ class Doximity < OmniAuth::Strategies::OAuth2

credentials do
prune({
access_token: raw_credential_info[:access_token],
refresh_token: raw_credential_info[:refresh_token],
expires_at: raw_credential_info[:expires_at],
access_token: raw_credential_info["access_token"],
refresh_token: raw_credential_info["refresh_token"],
expires_at: raw_credential_info["expires_at"],
scope: raw_credential_info["scope"],
token_type: raw_credential_info["token_type"]
})
Expand All @@ -60,7 +68,7 @@ def raw_subject_info
end

def raw_credential_info
@raw_credential_info ||= access_token.to_hash
@raw_credential_info ||= access_token.to_hash.with_indifferent_access
end

def authorize_params
Expand Down Expand Up @@ -93,6 +101,8 @@ def parse_id_token(token)

body, _ = JWT.decode(token, rsa_key.public_key, true, { algorithm: header["alg"] })
body
rescue JWT::VerificationError => e
raise OmniAuth::Doximity::JWTVerificationError(e, token)
end

def callback_url
Expand All @@ -108,9 +118,11 @@ def prune(hash)

def request_keys
url = options[:client_options][:site] + options[:client_options][:jwks_url]
uri = URI(url)
response = Net::HTTP.get(uri)
JSON.parse(response)["keys"]
response = Faraday.get(url)
if response.status != 200
raise OmniAuth::Doximity::JWKSRequestError(url, response)
end
MultiJson.load(response.body)["keys"]
end

def create_rsa_key(n, e)
Expand Down
6 changes: 4 additions & 2 deletions omniauth-doximity.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_runtime_dependency "json"
spec.add_runtime_dependency "activesupport"
spec.add_runtime_dependency "faraday"
spec.add_runtime_dependency "jwt"
spec.add_runtime_dependency "multi_json"
spec.add_runtime_dependency "omniauth-oauth2"
spec.add_runtime_dependency "openssl"

spec.add_development_dependency "bundler", "~> 2.1.4"
spec.add_development_dependency "bundler", "~> 2.3.11"
spec.add_development_dependency "dox-best-practices"
spec.add_development_dependency "dox-style"
spec.add_development_dependency "rake"
Expand Down
Binary file removed vendor/cache/json-2.6.1.gem
Binary file not shown.

0 comments on commit 765125c

Please sign in to comment.