Skip to content

Commit

Permalink
Add implicit flow.
Browse files Browse the repository at this point in the history
Co-authored-by: Nicholas Hance <nhance@reenhanced.com>
Co-authored-by: stevenvegt <steven.vandervegt@nedap.com>
  • Loading branch information
3 people committed Mar 4, 2018
1 parent 8cbcc15 commit 4c3e07d
Show file tree
Hide file tree
Showing 21 changed files with 469 additions and 12 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ OpenID Connect is a single-sign-on and identity layer with a [growing list of se

The following parts of [OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) are currently supported:
- [Authentication using the Authorization Code Flow](http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
- [Implicit Flow](http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth)
- [Requesting Claims using Scope Values](http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
- [UserInfo Endpoint](http://openid.net/specs/openid-connect-core-1_0.html#UserInfo)
- [Normal Claims](http://openid.net/specs/openid-connect-core-1_0.html#NormalClaims)
Expand Down Expand Up @@ -85,6 +86,12 @@ Verify your settings in `config/initializers/doorkeeper.rb`:
end
end
```
- `grant_flows`
- If you want to use `id_token` or `id_token token` response types you need to add `implicit_oidc` to `grant_flows`:

```ruby
grant_flows %w(authorization_code implicit_oidc)
```

The following settings are required in `config/initializers/doorkeeper_openid_connect.rb`:

Expand All @@ -96,7 +103,7 @@ The following settings are required in `config/initializers/doorkeeper_openid_co
- If you want to provide a different subject identifier to each client, use [pairwise subject identifier](http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes) with configurations like below.
```ruby
# config/initializers/doorkeeper_openid_connect.rb
# config/initializers/doorkeeper_openid_connect.rb
Doorkeeper::OpenidConnect.configure do
# ...
subject_types_supported [:pairwise]
Expand Down
2 changes: 1 addition & 1 deletion doorkeeper-openid_connect.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Gem::Specification.new do |spec|

spec.required_ruby_version = ">= 2.1"

spec.add_runtime_dependency 'doorkeeper', '~> 4.0'
spec.add_runtime_dependency 'doorkeeper', '~> 4.3'
spec.add_runtime_dependency 'json-jwt', '~> 1.6'

spec.add_development_dependency 'rspec-rails'
Expand Down
41 changes: 41 additions & 0 deletions lib/doorkeeper/oauth/id_token_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module Doorkeeper
module OAuth
class IdTokenRequest
attr_accessor :pre_auth, :auth, :resource_owner

def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@resource_owner = resource_owner
end

def authorize
if pre_auth.authorizable?
@auth = Authorization::Token.new(pre_auth, resource_owner)
@auth.issue_token
@response = response
else
@response = error_response
end
end

def deny
pre_auth.error = :access_denied
error_response
end

private

def response
id_token = Doorkeeper::OpenidConnect::IdToken.new(auth.token, pre_auth.nonce)

IdTokenResponse.new(pre_auth, auth, id_token)
end

def error_response
ErrorResponse.from_request pre_auth,
redirect_uri: pre_auth.redirect_uri,
response_on_fragment: true
end
end
end
end
28 changes: 28 additions & 0 deletions lib/doorkeeper/oauth/id_token_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Doorkeeper
module OAuth
class IdTokenResponse < BaseResponse
include OAuth::Helpers

attr_accessor :pre_auth, :auth, :id_token

def initialize(pre_auth, auth, id_token)
@pre_auth = pre_auth
@auth = auth
@id_token = id_token
end

def redirectable?
true
end

def redirect_uri
Authorization::URIBuilder.uri_with_fragment(
pre_auth.redirect_uri,
expires_in: auth.token.expires_in_seconds,
state: pre_auth.state,
id_token: id_token.as_jws_token
)
end
end
end
end
13 changes: 13 additions & 0 deletions lib/doorkeeper/oauth/id_token_token_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Doorkeeper
module OAuth
class IdTokenTokenRequest < IdTokenRequest
private

def response
id_token_token = Doorkeeper::OpenidConnect::IdTokenToken.new(auth.token, pre_auth.nonce)

IdTokenTokenResponse.new(pre_auth, auth, id_token_token)
end
end
end
end
16 changes: 16 additions & 0 deletions lib/doorkeeper/oauth/id_token_token_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Doorkeeper
module OAuth
class IdTokenTokenResponse < IdTokenResponse
def redirect_uri
Authorization::URIBuilder.uri_with_fragment(
pre_auth.redirect_uri,
access_token: auth.token.token,
token_type: auth.token.token_type,
expires_in: auth.token.expires_in_seconds,
state: pre_auth.state,
id_token: id_token.as_jws_token
)
end
end
end
end
10 changes: 10 additions & 0 deletions lib/doorkeeper/openid_connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@
require 'active_model'
require 'json/jwt'

require 'doorkeeper/request'
require 'doorkeeper/request/id_token'
require 'doorkeeper/request/id_token_token'
require 'doorkeeper/oauth/id_token_request'
require 'doorkeeper/oauth/id_token_token_request'
require 'doorkeeper/oauth/id_token_response'
require 'doorkeeper/oauth/id_token_token_response'

require 'doorkeeper/openid_connect/claims_builder'
require 'doorkeeper/openid_connect/claims/claim'
require 'doorkeeper/openid_connect/claims/normal_claim'
require 'doorkeeper/openid_connect/config'
require 'doorkeeper/openid_connect/response_types_config'
require 'doorkeeper/openid_connect/engine'
require 'doorkeeper/openid_connect/errors'
require 'doorkeeper/openid_connect/id_token'
require 'doorkeeper/openid_connect/id_token_token'
require 'doorkeeper/openid_connect/user_info'
require 'doorkeeper/openid_connect/version'

Expand Down
2 changes: 1 addition & 1 deletion lib/doorkeeper/openid_connect/id_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def claims
exp: expiration,
iat: issued_at,
nonce: nonce,
auth_time: auth_time,
auth_time: auth_time
}
end

Expand Down
33 changes: 33 additions & 0 deletions lib/doorkeeper/openid_connect/id_token_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Doorkeeper
module OpenidConnect
class IdTokenToken < IdToken
def claims
super.merge(at_hash: at_hash)
end

private

# The at_hash is build according to the following standard:
#
# http://openid.net/specs/openid-connect-implicit-1_0.html#IDToken
#
# at_hash:
# REQUIRED. Access Token hash value. If the ID Token is issued with an
# access_token in an Implicit Flow, this is REQUIRED, which is the case
# for this subset of OpenID Connect. Its value is the base64url encoding
# of the left-most half of the hash of the octets of the ASCII
# representation of the access_token value, where the hash algorithm
# used is the hash algorithm used in the alg Header Parameter of the
# ID Token's JOSE Header. For instance, if the alg is RS256, hash the
# access_token value with SHA-256, then take the left-most 128 bits and
# base64url-encode them. The at_hash value is a case-sensitive string.
def at_hash
sha256 = Digest::SHA256.new
token = @access_token.token
hashed_token = sha256.digest(token)
first_half = hashed_token[0...hashed_token.length / 2]
Base64.urlsafe_encode64(first_half).tr('=', '')
end
end
end
end
22 changes: 22 additions & 0 deletions lib/doorkeeper/openid_connect/oauth/authorization/token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Doorkeeper
module OpenidConnect
module OAuth
module Authorization
module Code
def issue_token
super.tap do |access_grant|
if pre_auth.nonce.present?
::Doorkeeper::OpenidConnect::Request.create!(
access_grant: access_grant,
nonce: pre_auth.nonce
)
end
end
end
end
end
end
end

OAuth::Authorization::Code.send :prepend, OpenidConnect::OAuth::Authorization::Code
end
14 changes: 9 additions & 5 deletions lib/doorkeeper/openid_connect/orm/active_record.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
require 'active_support/lazy_load_hooks'

module Doorkeeper
module OpenidConnect
module Orm
module ActiveRecord
def initialize_models!
super
require 'doorkeeper/openid_connect/orm/active_record/access_grant'
require 'doorkeeper/openid_connect/orm/active_record/request'
ActiveSupport.on_load(:active_record) do
require 'doorkeeper/openid_connect/orm/active_record/access_grant'
require 'doorkeeper/openid_connect/orm/active_record/request'

if Doorkeeper.configuration.active_record_options[:establish_connection]
[Doorkeeper::OpenidConnect::Request].each do |c|
c.send :establish_connection, Doorkeeper.configuration.active_record_options[:establish_connection]
if Doorkeeper.configuration.active_record_options[:establish_connection]
[Doorkeeper::OpenidConnect::Request].each do |c|
c.send :establish_connection, Doorkeeper.configuration.active_record_options[:establish_connection]
end
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions lib/doorkeeper/openid_connect/response_types_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Doorkeeper
module OpenidConnect
module ResponseTypeConfig
private def calculate_authorization_response_types
types = super
if grant_flows.include? 'implicit_oidc'
types << 'token'
types << 'id_token'
types << 'id_token token'
end
types
end
end
end

Config.send :prepend, OpenidConnect::ResponseTypeConfig
end
17 changes: 17 additions & 0 deletions lib/doorkeeper/request/id_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'doorkeeper/request/strategy'

module Doorkeeper
module Request
class IdToken < Strategy
delegate :current_resource_owner, to: :server

def pre_auth
server.context.send(:pre_auth)
end

def request
@request ||= OAuth::IdTokenRequest.new(pre_auth, current_resource_owner)
end
end
end
end
17 changes: 17 additions & 0 deletions lib/doorkeeper/request/id_token_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'doorkeeper/request/strategy'

module Doorkeeper
module Request
class IdTokenToken < Strategy
delegate :current_resource_owner, to: :server

def pre_auth
server.context.send(:pre_auth)
end

def request
@request ||= OAuth::IdTokenTokenRequest.new(pre_auth, current_resource_owner)
end
end
end
end
31 changes: 31 additions & 0 deletions spec/lib/id_token_token_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'rails_helper'

describe Doorkeeper::OpenidConnect::IdTokenToken do
subject { described_class.new(access_token, nonce) }
let(:access_token) { create :access_token, resource_owner_id: user.id }
let(:user) { create :user }
let(:nonce) { '123456' }

before do
allow(Time).to receive(:now) { Time.at 60 }
end

describe '#claims' do
it 'returns all default claims' do
# access token is from http://openid.net/specs/openid-connect-core-1_0.html
# so we can test `at_hash` value
access_token.update(token: 'jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y')

expect(subject.claims).to eq({
iss: 'dummy',
sub: user.id.to_s,
aud: access_token.application.uid,
exp: 180,
iat: 60,
nonce: nonce,
auth_time: 23,
at_hash: '77QmUPtjPfzWtF2AnpK9RQ'
})
end
end
end
39 changes: 39 additions & 0 deletions spec/lib/oauth/id_toke_token_request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'rails_helper'

describe Doorkeeper::OAuth::IdTokenTokenRequest do
let(:application) do
scopes = double(all: ['public'])
double(:application, id: 9990, scopes: scopes)
end

let(:pre_auth) do
double(
:pre_auth,
client: application,
redirect_uri: 'http://tst.com/cb',
state: nil,
scopes: Doorkeeper::OAuth::Scopes.from_string('public'),
error: nil,
authorizable?: true,
nonce: '12345'
)
end

let(:owner) do
double :owner, id: 7866
end

subject do
Doorkeeper::OAuth::IdTokenTokenRequest.new(pre_auth, owner)
end

it 'creates an access token' do
expect do
subject.authorize
end.to change { Doorkeeper::AccessToken.count }.by(1)
end

it 'returns id_token token response' do
expect(subject.authorize).to be_a(Doorkeeper::OAuth::IdTokenTokenResponse)
end
end
Loading

0 comments on commit 4c3e07d

Please sign in to comment.