GithubHelp home page GithubHelp logo

exop-group / doorkeeper-device_authorization_grant Goto Github PK

View Code? Open in Web Editor NEW
29.0 9.0 10.0 151 KB

OAuth 2.0 Device Authorization Grant extension for Doorkeeper

Home Page: https://rubygems.org/gems/doorkeeper-device_authorization_grant

License: MIT License

Ruby 93.87% HTML 4.91% JavaScript 0.62% CSS 0.60%

doorkeeper-device_authorization_grant's Introduction

Doorkeeper::DeviceAuthorizationGrant

OAuth 2.0 device authorization grant extension for Doorkeeper.

This library implements the OAuth 2.0 device authorization grant (RFC 8628) for Ruby on Rails applications on top of the Doorkeeper OAuth 2.0 framework.

Installation

Add this line to your application's Gemfile:

gem 'doorkeeper-device_authorization_grant'

And then execute:

$ bundle

Or install it yourself as:

$ gem install doorkeeper-device_authorization_grant

Run the installation generator to update routes and create a dedicated initializer:

$ rails generate doorkeeper:device_authorization_grant:install

Generate a migration for Active Record (other ORMs are currently not supported):

$ rails doorkeeper_device_authorization_grant_engine:install:migrations

Configuration

Doorkeeper configuration

In your Doorkeeper initializer (usually config/initializers/doorkeeper.rb), enable the new grant flow extension, adding to the grant_flows option the device_code string. For example:

  # config/initializers/doorkeeper.rb
  
  Doorkeeper.configure do
    # ... 
  
    grant_flows [
      'device_code',
 
      # together with all the other grant flows you already enabled, for example:
      'authorization_code',
      'client_credentials'
      # ...
    ]

    # ...
  end

Device Authorization Grant configuration

The gem's installation scripts automatically creates a new initializer file: config/initializers/doorkeeper_device_authorization_grant.rb. Here you can adjust the configuration parameters according to your needs.

Routes

The gem's installation scripts automatically modify your config/routes.rb file, adding the default routes to the controllers described above. The routes file should then look like this:

Rails.application.routes.draw do
  use_doorkeeper_device_authorization_grant
  # your routes ...
end

This is enough to add to your app the following default routes:

                               Prefix  Verb  URI                      Controller#Action
            oauth_device_codes_create  POST  /oauth/authorize_device  doorkeeper/device_authorization_grant/device_codes#create
    oauth_device_authorizations_index  GET   /oauth/device            doorkeeper/device_authorization_grant/device_authorizations#index
oauth_device_authorizations_authorize  POST  /oauth/device            doorkeeper/device_authorization_grant/device_authorizations#authorize

The routing method use_doorkeeper_device_authorization_grant allows extra customization, just like use_doorkeeper (see Doorkeeper Wiki - Customizing routes).

This Gem defines two Rails controllers:

  • DeviceCodesController serves Device Authorization requests, as described by RFC 8628, sections 3.1 and 3.2.
  • DeviceAuthorizationsController provides a bare-bones implementation of a verification web page which allows an authenticated resource-owner to authorize a device, by providing an end-user code.

You can change the controllers to your custom controllers with the controller option:

Rails.application.routes.draw do
  use_doorkeeper_device_authorization_grant do
    # it accepts :device_authorizations and :device_codes
    controller device_authorizations: 'custom_device_authorizations'
  end
end

Be sure to use the same superclasses of the original controllers (or something compatible).

You can set custom aliases with as:

Rails.application.routes.draw do
  use_doorkeeper_device_authorization_grant do
    # it accepts :device_authorizations and :device_codes
    as device_codes: :custom_device
  end
end

You can skip routes with skip_controllers:

Rails.application.routes.draw do
  use_doorkeeper_device_authorization_grant do
    # it accepts :device_authorizations and :device_codes
    skip_controllers :device_authorizations
  end
end

The default scope is oauth. You can provide a custom scope like this:

Rails.application.routes.draw do
  use_doorkeeper_device_authorization_grant scope: 'oauth2'
end

Usage

The following sections show the typical steps of a device authorization flow. Default configuration and routes are assumed.

Device Authorization Request

Reference: RFC 8628, section 3.1 - Device Authorization Request.

First of all, a Device Client can perform a Device Authorization Request to the Authorization Server (your Rails application, with Doorkeeper and this gem extension) like this:

POST /oauth/authorize_device HTTP/1.1
Content-Type: application/x-www-form-urlencoded

client_id=1406020730&scope=example_scope

Device Authorization Response

Reference: RFC 8628, section 3.2 - Device Authorization Response.

The Authorization Server responds with a Device Authorization Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
    "user_code": "0A44L90H",
    "verification_uri": "https://example.com/oauth/device",
    "verification_uri_complete": "https://example.com/oauth/device?user_code=0A44L90H",
    "expires_in": 300,
    "interval": 5
}

User interaction

Reference: RFC 8628, section 3.3 - User Interaction.

The Device Client can now display to the end user the user_code and the verification_uri (or somehow make use of verification_uri_complete, in special cases).

The user should visit URI in a user agent on a secondary device (for example, in a browser on their mobile phone) and enter the user code.

During the user interaction, the device continuously polls the token endpoint with the device_code, as detailed in the next section, until the user completes the interaction, the code expires, or another error occurs.

The default Rails route provided by this Gem, /oauth/device, allows an authenticated request owner (for example, a user) to manually verify the user code.

Device Access Token Request / polling

Reference: RFC 8628, section 3.4 - Device Access Token Request.

After displaying instructions to the user, the Device Client should create a Device Access Token Request and send it to the token endpoint (provided by Dorkeeper), for example:

POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730

The response to this request is defined in the next section. It is expected for the Device Client to try the access token request repeatedly in a polling fashion, based on the error code in the response. The polling time interval was possibly included in the Device Authorization Response, but it is optional; if no value was provided, the client MUST use 5 seconds as the default.

Device Access Token Response

Reference: RFC 8628, section 3.5 - Device Access Token Response.

Please refer to the RFC document for exhaustive documentation. Here we show just some possible responses.

While the authorization request is still pending, and the device-code token is not expired, the response contains an authorization_pending error:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{ "error": "authorization_pending", "error_description": "..." }

The client should simply continue with further polling requests.

If the client requests are too close in time, a slow_down error is returned:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{ "error": "slow_down", "error_description": "..." }

The client can still continue with polling requests, but the polling time interval MUST be increased by 5 seconds for all subsequent requests.

If the device_code has expired, the response contains the expired_token error:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{ "error": "expired_token", "error_description": "..." }

The client should stop polling, and may commence a new device authorization request (possibly upon waiting for further user interaction).

Once the user has successfully authorized the device, a successful response will be eventually returned. This is a standard OAuth 2.0 response, described in Section 5.1 of [RFC6749]. Here is a typical bearer token response:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "access_token": "FkPeBMF8Ab0zkYj6vQLZCxZ5OP0Hrd7ST3RS99x7nRM",
    "token_type": "Bearer",
    "expires_in": 7200,
    "scope": "read",
    "created_at": 1593096829
}

The device authentication flow is now complete, and the token data can be used to authenticate requests against the authorization and/or resource server.

Example Application

Here you can find an example Rails application which uses this gem, together with a little HTML/JS client to try out the device flow:

https://github.com/exop-group/doorkeeper-device-flow-example

License

The gem is available as open source under the terms of the MIT License.

doorkeeper-device_authorization_grant's People

Contributors

dgraham avatar iheanyi avatar irminsul avatar marco-nicola avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

doorkeeper-device_authorization_grant's Issues

Broken with Doorkeeper 5.6.5

Doorkeeper 5.6.5 includes doorkeeper-gem/doorkeeper#1602 which breaks this gem since the signature of find_or_create_access_token is changed for the custom attributes as discussed in that PR.

I guess Doorkeeper didn't consider this internal change to need a new version, so it looks like this gem might be using internals of Doorkeeper that it shouldn't? Is there a good way to fix this?

Are optional scopes supported?

@marco-nicola sorry to bug you, but it seems like optional scopes are ignored -- when specifying scopes on the authorize_device request, e.g. client_id=xxxxxx&scope=write, the returned access token only includes the default scope:

{
  "access_token": "xGHWsOTDfnbkAlMpZ2im5RwrUm2kYbFfZiyj-2wlX5Y",
  "token_type": "Bearer",
  "expires_in": 7200,
  "scope": "public",
  "created_at": 1610392346
}

Add Support for Per-App Scope Validation

First off, my apologies if this project isn't accepting feature requests or this is a weird-ish one. I'm still actively learning Ruby so I may be going about this all wrong.

I have an application which permits other users to create OAuth applications, where different applications may have different scopes approved for their use (stored in the scopes column of the applications table). In order to facilitate this, I borrowed the existing logic from DeviceGrantMixin for system-level scope enforcement:

  validate :validate_scopes_approved

  def validate_scopes_approved
    scopes_valid = scopes.present? && Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
      scope_str: scopes.to_s,
      server_scopes: Doorkeeper.config.scopes,
      app_scopes: application.scopes,
      grant_type: Doorkeeper::DeviceAuthorizationGrant::OAuth::DEVICE_CODE
    )

    errors.add(:scopes, :invalid_scope) unless scopes_valid
  end

This, however, nets in a 500 exception when an application attempts to use an invalid scope. While this does block the action as intended, this does not behave in an expected manner (e.g. the nice error message that shows when client_id is missing). I've also been able to reproduce this 500 behavior when passing in a fully-invalid scope while enforce_configured_scopes is set.

I've attempted to trace this a bit further, and it appears as though this is caused by the fact that DeviceAuthorizationRequest is not actually aware of scope authorization. Unfortunately, it seems difficult (if even possible? I'm honestly not sure how) to monkeypatch the appropriate validation into that class. Seeing as Doorkeeper proper respects this for its Authorization Code flows, I'd like to see if it's possible to have this added as a feature to the gem. Alternatively, if there's a better way to handle this case (or a way to patch this without forking the library), I'm absolutely willing to implement that instead.

Thanks!

Missing allowed grant flows validation configuration

After looking through a few places the available flows validation seems to be missing:

 option :allow_grant_flow_for_client,    default: ->(_grant_flow, _client) { true }

From the client_credentials flow validation:

        validate :client_supports_grant_flow, error: :unauthorized_client
...
        def validate_client_supports_grant_flow
          return if @client.blank?

          Doorkeeper.config.allow_grant_flow_for_client?(
            Doorkeeper::OAuth::CLIENT_CREDENTIALS,
            @client.application,
          )
        end

Which means if you only have client_credentials or authorization_code enabled on a grant_flow on doorkeeper this flow is not honoring the validation. Let me know if I have misunderstood.

https://github.com/doorkeeper-gem/doorkeeper/blob/f02fcb447a0b39c43cae350a600b853a0e69ee60/lib/doorkeeper/oauth/client_credentials/validator.rb#L31
https://github.com/doorkeeper-gem/doorkeeper/blob/f02fcb447a0b39c43cae350a600b853a0e69ee60/lib/doorkeeper/config.rb#L285

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.