grape_oauth2 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +18 -0
  5. data/.travis.yml +42 -0
  6. data/Gemfile +23 -0
  7. data/README.md +820 -0
  8. data/Rakefile +11 -0
  9. data/gemfiles/active_record.rb +25 -0
  10. data/gemfiles/mongoid.rb +14 -0
  11. data/gemfiles/sequel.rb +24 -0
  12. data/grape_oauth2.gemspec +27 -0
  13. data/grape_oauth2.png +0 -0
  14. data/lib/grape_oauth2.rb +129 -0
  15. data/lib/grape_oauth2/configuration.rb +143 -0
  16. data/lib/grape_oauth2/configuration/class_accessors.rb +36 -0
  17. data/lib/grape_oauth2/configuration/validation.rb +71 -0
  18. data/lib/grape_oauth2/endpoints/authorize.rb +34 -0
  19. data/lib/grape_oauth2/endpoints/token.rb +72 -0
  20. data/lib/grape_oauth2/gem_version.rb +24 -0
  21. data/lib/grape_oauth2/generators/authorization.rb +44 -0
  22. data/lib/grape_oauth2/generators/base.rb +26 -0
  23. data/lib/grape_oauth2/generators/token.rb +62 -0
  24. data/lib/grape_oauth2/helpers/access_token_helpers.rb +54 -0
  25. data/lib/grape_oauth2/helpers/oauth_params.rb +41 -0
  26. data/lib/grape_oauth2/mixins/active_record/access_grant.rb +47 -0
  27. data/lib/grape_oauth2/mixins/active_record/access_token.rb +75 -0
  28. data/lib/grape_oauth2/mixins/active_record/client.rb +35 -0
  29. data/lib/grape_oauth2/mixins/mongoid/access_grant.rb +58 -0
  30. data/lib/grape_oauth2/mixins/mongoid/access_token.rb +88 -0
  31. data/lib/grape_oauth2/mixins/mongoid/client.rb +41 -0
  32. data/lib/grape_oauth2/mixins/sequel/access_grant.rb +68 -0
  33. data/lib/grape_oauth2/mixins/sequel/access_token.rb +86 -0
  34. data/lib/grape_oauth2/mixins/sequel/client.rb +46 -0
  35. data/lib/grape_oauth2/responses/authorization.rb +10 -0
  36. data/lib/grape_oauth2/responses/base.rb +56 -0
  37. data/lib/grape_oauth2/responses/token.rb +10 -0
  38. data/lib/grape_oauth2/scopes.rb +74 -0
  39. data/lib/grape_oauth2/strategies/authorization_code.rb +38 -0
  40. data/lib/grape_oauth2/strategies/base.rb +47 -0
  41. data/lib/grape_oauth2/strategies/client_credentials.rb +20 -0
  42. data/lib/grape_oauth2/strategies/password.rb +22 -0
  43. data/lib/grape_oauth2/strategies/refresh_token.rb +47 -0
  44. data/lib/grape_oauth2/unique_token.rb +20 -0
  45. data/lib/grape_oauth2/version.rb +14 -0
  46. data/spec/configuration/config_spec.rb +231 -0
  47. data/spec/configuration/version_spec.rb +12 -0
  48. data/spec/dummy/endpoints/custom_authorization.rb +25 -0
  49. data/spec/dummy/endpoints/custom_token.rb +35 -0
  50. data/spec/dummy/endpoints/status.rb +25 -0
  51. data/spec/dummy/grape_oauth2_config.rb +11 -0
  52. data/spec/dummy/orm/active_record/app/config/db.rb +7 -0
  53. data/spec/dummy/orm/active_record/app/models/access_code.rb +3 -0
  54. data/spec/dummy/orm/active_record/app/models/access_token.rb +3 -0
  55. data/spec/dummy/orm/active_record/app/models/application.rb +3 -0
  56. data/spec/dummy/orm/active_record/app/models/application_record.rb +3 -0
  57. data/spec/dummy/orm/active_record/app/models/user.rb +10 -0
  58. data/spec/dummy/orm/active_record/app/twitter.rb +36 -0
  59. data/spec/dummy/orm/active_record/config.ru +7 -0
  60. data/spec/dummy/orm/active_record/db/schema.rb +53 -0
  61. data/spec/dummy/orm/mongoid/app/config/db.rb +6 -0
  62. data/spec/dummy/orm/mongoid/app/config/mongoid.yml +21 -0
  63. data/spec/dummy/orm/mongoid/app/models/access_code.rb +3 -0
  64. data/spec/dummy/orm/mongoid/app/models/access_token.rb +3 -0
  65. data/spec/dummy/orm/mongoid/app/models/application.rb +3 -0
  66. data/spec/dummy/orm/mongoid/app/models/user.rb +11 -0
  67. data/spec/dummy/orm/mongoid/app/twitter.rb +34 -0
  68. data/spec/dummy/orm/mongoid/config.ru +5 -0
  69. data/spec/dummy/orm/sequel/app/config/db.rb +1 -0
  70. data/spec/dummy/orm/sequel/app/models/access_code.rb +4 -0
  71. data/spec/dummy/orm/sequel/app/models/access_token.rb +4 -0
  72. data/spec/dummy/orm/sequel/app/models/application.rb +4 -0
  73. data/spec/dummy/orm/sequel/app/models/application_record.rb +2 -0
  74. data/spec/dummy/orm/sequel/app/models/user.rb +11 -0
  75. data/spec/dummy/orm/sequel/app/twitter.rb +47 -0
  76. data/spec/dummy/orm/sequel/config.ru +5 -0
  77. data/spec/dummy/orm/sequel/db/schema.rb +50 -0
  78. data/spec/lib/scopes_spec.rb +50 -0
  79. data/spec/mixins/active_record/access_token_spec.rb +185 -0
  80. data/spec/mixins/active_record/client_spec.rb +95 -0
  81. data/spec/mixins/mongoid/access_token_spec.rb +185 -0
  82. data/spec/mixins/mongoid/client_spec.rb +95 -0
  83. data/spec/mixins/sequel/access_token_spec.rb +185 -0
  84. data/spec/mixins/sequel/client_spec.rb +96 -0
  85. data/spec/requests/flows/authorization_code_spec.rb +67 -0
  86. data/spec/requests/flows/client_credentials_spec.rb +101 -0
  87. data/spec/requests/flows/password_spec.rb +210 -0
  88. data/spec/requests/flows/refresh_token_spec.rb +222 -0
  89. data/spec/requests/flows/revoke_token_spec.rb +103 -0
  90. data/spec/requests/protected_resources_spec.rb +64 -0
  91. data/spec/spec_helper.rb +60 -0
  92. data/spec/support/api_helper.rb +11 -0
  93. metadata +257 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f5684304c79fc0d0110edf0f0cfccf74f965f120
4
+ data.tar.gz: 20ed4ecda0c0406ade366594abd43d89b26f79fd
5
+ SHA512:
6
+ metadata.gz: 8720ebaef81e37b3b9160e434f10823ebf10a1959718048cd3c80cbe125d0ddea1c5ef78632fa899317634bdd07ed15ac81fe67d32f9d58a2a963253e3ebb6c2
7
+ data.tar.gz: 0ce5d8636c5c4b5920b879de0fd353c740d6bd2b2fc73cd5634b351ebc4eddb75a4b6b9d63295e18b8d34a037345ba43e7a053dc172fd866c6cad6cdca9b52e4
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .bundle/
2
+ .rbx
3
+ *.rbc
4
+ log/*.log
5
+ .rvmrc
6
+ /.idea
7
+ gemfiles/*.lock
8
+ Gemfile.lock
9
+ coverage/
10
+ .yardoc/
11
+ doc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ LineLength:
2
+ Max: 120
3
+ AllCops:
4
+ Exclude:
5
+ - 'spec/**/*'
6
+ DisplayCopNames: true
7
+ Rails:
8
+ Enabled: true
9
+ Documentation:
10
+ Enabled: false
11
+ Style/EndOfLine:
12
+ Enabled: false
13
+ Rails/TimeZone:
14
+ Enabled: false
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - lib/grape_oauth2/mixins/**/*
18
+ - lib/grape_oauth2/endpoints/*
data/.travis.yml ADDED
@@ -0,0 +1,42 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ bundler_args: --without yard guard benchmarks
5
+
6
+ services:
7
+ - mongodb
8
+
9
+ before_install:
10
+ - gem install bundler -v '~> 1.10'
11
+
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
15
+ include:
16
+ - rvm: 2.2.4
17
+ gemfile: gemfiles/active_record.rb
18
+ env: ORM=active_record
19
+ - rvm: 2.2.4
20
+ gemfile: gemfiles/sequel.rb
21
+ env: ORM=sequel
22
+ - rvm: 2.2.4
23
+ gemfile: gemfiles/mongoid.rb
24
+ env: ORM=mongoid
25
+ - rvm: 2.3.1
26
+ gemfile: gemfiles/active_record.rb
27
+ env: ORM=active_record
28
+ - rvm: 2.3.1
29
+ gemfile: gemfiles/sequel.rb
30
+ env: ORM=sequel
31
+ - rvm: 2.3.1
32
+ gemfile: gemfiles/mongoid.rb
33
+ env: ORM=mongoid
34
+ - rvm: ruby-head
35
+ gemfile: gemfiles/active_record.rb
36
+ env: ORM=active_record
37
+ - rvm: ruby-head
38
+ gemfile: gemfiles/sequel.rb
39
+ env: ORM=sequel
40
+ - rvm: ruby-head
41
+ gemfile: gemfiles/mongoid.rb
42
+ env: ORM=mongoid
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'grape', '~> 0.16'
6
+ gem 'rack-oauth2'
7
+
8
+ gem 'activerecord'
9
+ gem 'bcrypt'
10
+
11
+ group :test do
12
+ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
13
+ gem 'sqlite3'
14
+ end
15
+
16
+ gem 'rspec-rails', '~> 3.5'
17
+ gem 'coveralls', require: false
18
+ gem 'database_cleaner'
19
+ gem 'rack-test', require: 'rack/test'
20
+ gem 'otr-activerecord'
21
+ end
22
+
23
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
data/README.md ADDED
@@ -0,0 +1,820 @@
1
+ <p align="center">
2
+ <img alt="Grape::OAuth2 - OAuth2 provider for Grape" src="https://raw.githubusercontent.com/nbulaj/grape_oauth2/master/grape_oauth2.png">
3
+ </p>
4
+
5
+ # Grape::OAuth2
6
+ [![Build Status](https://travis-ci.org/nbulaj/grape_oauth2.svg?branch=master)](https://travis-ci.org/nbulaj/grape_oauth2)
7
+ [![Dependency Status](https://gemnasium.com/nbulaj/grape_oauth2.svg)](https://gemnasium.com/nbulaj/grape_oauth2)
8
+ [![Coverage Status](https://coveralls.io/repos/github/nbulaj/grape_oauth2/badge.svg)](https://coveralls.io/github/nbulaj/grape_oauth2)
9
+ [![Code Climate](https://codeclimate.com/github/nbulaj/grape_oauth2/badges/gpa.svg)](https://codeclimate.com/github/nbulaj/grape_oauth2)
10
+ [![Inline docs](http://inch-ci.org/github/nbulaj/grape_oauth2.svg?branch=master)](http://inch-ci.org/github/nbulaj/grape_oauth2)
11
+ [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg)](#license)
12
+
13
+ This gem adds a flexible OAuth2 ([RFC 6749](http://www.rfc-editor.org/rfc/rfc6749.txt)) server authorization and
14
+ endpoints protection to your [Grape](https://github.com/ruby-grape/grape) API project with any ORM / ODM / PORO.
15
+
16
+ **Currently under development**.
17
+
18
+ Implemented features (flows):
19
+
20
+ - Resource Owner Password Credentials
21
+ - Client Credentials
22
+ - Refresh token
23
+ - Token revocation
24
+ - Access Token Scopes
25
+
26
+ Supported token types:
27
+
28
+ * Bearer
29
+
30
+ _In progress_:
31
+
32
+ - Authorization Code Flow (partially implemented)
33
+ - Access Grants
34
+ - Implicit Grant
35
+
36
+ ## Documentation valid for `master` branch
37
+
38
+ Please check the documentation for the version of `Grape::OAuth2` you are using in:
39
+ https://github.com/nbulaj/grape_oauth2/releases
40
+
41
+ - See the [Wiki](https://github.com/nbulaj/grape_oauth2/wiki)
42
+
43
+ ## Table of Contents
44
+
45
+ - [Installation](#installation)
46
+ - [Configuration](#configuration)
47
+ - [ActiveRecord](#activerecord)
48
+ - [Sequel](#sequel)
49
+ - [Mongoid](#mongoid)
50
+ - [Other ORMs](#other-orms)
51
+ - [Client](#client)
52
+ - [AccessToken](#accesstoken)
53
+ - [ResourceOwner](#resourceowner)
54
+ - [Usage examples](#usage-examples)
55
+ - [I'm lazy, give me all out of the box!](#im-lazy-give-me-all-out-of-the-box)
56
+ - [Hey, I wanna control all the authentication process!](#hey-i-wanna-control-all-the-authentication-process)
57
+ - [Override default mixins](#override-default-mixins)
58
+ - [Custom authentication endpoints](#custom-authentication-endpoints)
59
+ - [Custom Access Token authenticator](#custom-access-token-authenticator)
60
+ - [Custom scopes validation](#custom-scopes-validation)
61
+ - [Custom token generator](#custom-token-generator)
62
+ - [Process token on Refresh (protect against Replay Attacks)](#process-token-on-refresh-protect-against-replay-attacks)
63
+ - [Errors (exceptions) handling](#errors-exceptions-handling)
64
+ - [Example App](#example-app)
65
+ - [Contributing](#contributing)
66
+ - [License](#license)
67
+
68
+ ## Installation
69
+
70
+ **Grape::OAuth2** gem requires only `Grape` and `Rack::OAuth2` gems as the dependency.
71
+ Yes, no Rails, ActiveRecord or any other libs or huge frameworks :+1:
72
+
73
+ If you are using bundler, first add `'grape_oauth2'` to your Gemfile:
74
+
75
+ ```ruby
76
+ gem 'grape_oauth2', git: 'https://github.com/nbulaj/grape_oauth2.git'
77
+ ```
78
+
79
+ And run:
80
+
81
+ ```sh
82
+ bundle install
83
+ ```
84
+
85
+ If you running your Grape API with `rackup` and using the [gem from git source](http://bundler.io/git.html), then
86
+ you need to explicitly require bundler in the `config.ru`:
87
+
88
+ ```ruby
89
+ require 'bundler/setup'
90
+ Bundler.setup
91
+ ```
92
+
93
+ or run your app with bundle exec command:
94
+
95
+ ```
96
+ > bundle exec rackup config.ru
97
+ [2016-11-19 02:35:33] INFO WEBrick 1.3.1
98
+ [2016-11-19 02:35:33] INFO ruby 2.3.1 (2016-04-26) [i386-mingw32]
99
+ [2016-11-19 02:35:33] INFO WEBrick::HTTPServer#start: pid=5472 port=9292
100
+ ```
101
+
102
+ ## Configuration
103
+
104
+ Main `Grape::OAuth2` configuration must be placed in `config/initializers/` (in case you are using [Rails](https://github.com/rails/rails))
105
+ or in some place, that will be processed at the application startup:
106
+
107
+ ```ruby
108
+ Grape::OAuth2.configure do |config|
109
+ # Access Tokens lifetime (expires in)
110
+ config.access_token_lifetime = 7200 # in seconds (2.hours for Rails), `nil` if never expires
111
+
112
+ # Authorization Code lifetime
113
+ # config.authorization_code_lifetime = 7200 # in seconds (2.hours for Rails)
114
+
115
+ # Allowed OAuth2 Authorization Grants (default is %w(password client_credentials)
116
+ config.allowed_grant_types = %w(password client_credentials refresh_token)
117
+
118
+ # Issue access tokens with refresh token (default is false)
119
+ config.issue_refresh_token = true
120
+
121
+ # Process Access Token that was used for the Refresh Token Flow (default is :nothing).
122
+ # Could be a symbol (Access Token instance must respond to it)
123
+ # or block with refresh token as an argument.
124
+ # config.on_refresh = :nothing
125
+
126
+ # WWW-Authenticate Realm (default is "OAuth 2.0")
127
+ # config.realm = 'My API'
128
+
129
+ # Access Token authenticator block.
130
+ # config.token_authenticator do |request|
131
+ # AccessToken.authenticate(request.access_token) || request.invalid_token!
132
+ # end
133
+
134
+ # Scopes validator class (default is Grape::OAuth2::Scopes).
135
+ # config.scopes_validator_class_name = 'MyCustomValidator'
136
+
137
+ # Token generator class (default is Grape::OAuth2::UniqueToken).
138
+ # Must respond to `self.generate(payload = {}, options = {})`.
139
+ # config.token_generator_class_name = 'JWTGenerator'
140
+
141
+ # Classes for OAuth2 Roles
142
+ config.client_class_name = 'Application'
143
+ config.access_token_class_name = 'AccessToken'
144
+ config.resource_owner_class_name = 'User'
145
+ end
146
+ ```
147
+
148
+ Currently implemented (partly on completely) grant types: _password, client_credentials, refresh_token_.
149
+
150
+ As you know, OAuth2 workflow implies the existence of the next three roles: **Access Token**, **Client** and **Resource Owner**.
151
+ So your project must include 3 classes (models) - _AccessToken_, _Application_ and _User_ for example. The gem needs to know
152
+ what classes it work, so you need to create them and configure `Grape::OAuth2`.
153
+
154
+ `resource_owner_class` must have a `self.oauth_authenticate(client, username, password)` method, that returns an instance of the
155
+ class if authentication successful (`username` and `password` matches for example) and `false` or `nil` in other cases.
156
+
157
+ ```ruby
158
+ # app/models/user.rb
159
+ class User < ApplicationRecord
160
+ has_secure_password
161
+
162
+ def self.oauth_authenticate(_client, username, password)
163
+ # find the user by it username
164
+ user = find_by(username: username)
165
+ return if user.nil?
166
+
167
+ # check the password
168
+ user.authenticate(password)
169
+ end
170
+ end
171
+ ```
172
+
173
+ `client_class`, `access_token_class` and `resource_owner_class` objects must contain a specific set of API (methods), that are
174
+ called by the gem. `Grape::OAuth2` includes predefined mixins for the projects that use the `ActiveRecord` or `Sequel` ORMs,
175
+ and you can just include them into your models.
176
+
177
+ ### ActiveRecord
178
+
179
+ ```ruby
180
+ # app/models/access_token.rb
181
+ class AccessToken < ApplicationRecord
182
+ include Grape::OAuth2::ActiveRecord::AccessToken
183
+ end
184
+
185
+ # app/models/application.rb
186
+ class Application < ApplicationRecord
187
+ include Grape::OAuth2::ActiveRecord::Client
188
+ end
189
+ ```
190
+
191
+ Migration for the simplest use case of the gem looks as follows:
192
+
193
+ ```ruby
194
+ ActiveRecord::Schema.define(version: 3) do
195
+ # All the columns are custom
196
+ create_table :users do |t|
197
+ t.string :name
198
+ t.string :username
199
+ t.string :password_digest
200
+ end
201
+
202
+ # Required columns: :key & :secret
203
+ create_table :applications do |t|
204
+ t.string :name
205
+ t.string :key
206
+ t.string :secret
207
+
208
+ t.timestamps null: false
209
+ end
210
+
211
+ add_index :applications, :key, unique: true
212
+
213
+ # Required columns: :client_id, :resource_owner_id, :token, :expires_at, :revoked_at, :refresh_token
214
+ create_table :access_tokens do |t|
215
+ t.integer :resource_owner_id
216
+ t.integer :client_id
217
+
218
+ t.string :token, null: false
219
+ t.string :refresh_token
220
+ t.string :scopes, default: ''
221
+
222
+ t.datetime :expires_at
223
+ t.datetime :revoked_at
224
+ t.datetime :created_at, null: false
225
+ end
226
+
227
+ add_index :access_tokens, :token, unique: true
228
+ add_index :access_tokens, :resource_owner_id
229
+ add_index :access_tokens, :client_id
230
+ add_index :access_tokens, :refresh_token, unique: true
231
+ end
232
+ ```
233
+
234
+ ### Sequel
235
+
236
+ ```ruby
237
+ # app/models/access_token.rb
238
+ class AccessToken < Sequel::Model
239
+ include Grape::OAuth2::Sequel::AccessToken
240
+ end
241
+
242
+ # app/models/application.rb
243
+ class Application < Sequel::Model
244
+ include Grape::OAuth2::Sequel::Client
245
+ end
246
+ ```
247
+
248
+ Migration for the simplest use case of the gem looks as follows:
249
+
250
+ ```ruby
251
+ DB.create_table :applications do
252
+ primary_key :id
253
+
254
+ column :name, String, size: 255, null: false
255
+ column :key, String, size: 255, null: false, index: { unique: true }
256
+ column :secret, String, size: 255, null: false
257
+
258
+
259
+ column :redirect_uri, String
260
+
261
+ column :created_at, DateTime
262
+ column :updated_at, DateTime
263
+ end
264
+
265
+ DB.create_table :access_tokens do
266
+ primary_key :id
267
+ column :client_id, Integer
268
+ column :resource_owner_id, Integer, index: true
269
+
270
+ column :token, String, size: 255, null: false, index: { unique: true }
271
+
272
+ column :refresh_token, String, size: 255, index: { unique: true }
273
+
274
+ column :expires_at, DateTime
275
+ column :revoked_at, DateTime
276
+ column :created_at, DateTime, null: false
277
+ column :scopes, String, size: 255
278
+ end
279
+
280
+ DB.create_table :users do
281
+ primary_key :id
282
+ column :name, String, size: 255
283
+ column :username, String, size: 255
284
+ column :created_at, DateTime
285
+ column :updated_at, DateTime
286
+ column :password_digest, String, size: 255
287
+ end
288
+ ```
289
+
290
+ ### Mongoid
291
+
292
+ ```ruby
293
+ # app/models/access_token.rb
294
+ class AccessToken
295
+ include Grape::OAuth2::Mongoid::AccessToken
296
+ end
297
+
298
+ # app/models/application.rb
299
+ class Application
300
+ include Grape::OAuth2::Mongoid::Client
301
+ end
302
+
303
+ # app/models/user.rb
304
+ class User
305
+ include Mongoid::Document
306
+ include Mongoid::Timestamps
307
+
308
+ field :username, type: String
309
+ field :password, type: String
310
+
311
+ def self.oauth_authenticate(_client, username, password)
312
+ find_by(username: username, password: password)
313
+ end
314
+ end
315
+ ```
316
+
317
+ ### Other ORMs
318
+
319
+ If you want to use `Grape::OAuth2` gem, but your project doesn't use `ActiveRecord`, `Sequel` or `Mongoid`, then you can
320
+ create at least 3 classes (models) to cover OAuth2 roles and define a specific set ot API for them as described below.
321
+
322
+ #### Client
323
+
324
+ Class that represents an OAuth2 Client should contain the following API:
325
+
326
+ ```ruby
327
+ class Client
328
+ # ...
329
+
330
+ def self.authenticate(key, secret = nil)
331
+ # Should return a Client instance matching the
332
+ # key & secret provided (`secret` is optional).
333
+ end
334
+ end
335
+ ```
336
+
337
+ #### AccessToken
338
+
339
+ For the class that represents an OAuth2 Access Token you must define the next API:
340
+
341
+ ```ruby
342
+ class AccessToken
343
+ # ...
344
+
345
+ def self.create_for(client, resource_owner, scopes = nil)
346
+ # Creates the record in the database for the provided client and
347
+ # resource owner with specific scopes (if present).
348
+ # Returns an instance of that record.
349
+ end
350
+
351
+ def self.authenticate(token, type: :access_token)
352
+ # Returns an Access Token instance matching the token provided.
353
+ # Access Token can be searched by token or refresh token value. In the
354
+ # first case :type option must be set to :access_token (default), in
355
+ # the second case - to the :refresh_token.
356
+ # Note that you MAY include expired access tokens in the result
357
+ # of this method so long as you implement an instance `#expired?`
358
+ # method.
359
+ end
360
+
361
+ def client
362
+ # Returns associated Client instance. Always must be present!
363
+ # For ORM objects it can be an association (`belongs_to :client` for ActiveRecord).
364
+ end
365
+
366
+ def resource_owner
367
+ # Returns associated Resource Owner instance.
368
+ # Can return `nil` (for Client Credentials flow as an example).
369
+ # For ORM objects it can be an association (`belongs_to :resource_owner` for ActiveRecord).
370
+ end
371
+
372
+ def scopes
373
+ # Returns Access Token authorised set of scopes. Can be a space-separated String,
374
+ # Array or any object, that responds to `to_a`.
375
+ end
376
+
377
+ def expired?
378
+ # true if the Access Token has reached its expiration.
379
+ end
380
+
381
+ def revoked?
382
+ # true if the Access Token was revoked.
383
+ end
384
+
385
+ def revoke!(revoked_at = Time.now)
386
+ # Revokes an Access Token (by setting its :revoked_at attribute to the
387
+ # specific time for example).
388
+ end
389
+
390
+ def to_bearer_token
391
+ # Returns a Hash of Bearer token attributes like the following:
392
+ # access_token: '', # - required
393
+ # refresh_token: '', # - optional
394
+ # token_type: 'bearer', # - required
395
+ # expires_in: '', # - required
396
+ # scope: '' # - optional
397
+ end
398
+ end
399
+ ```
400
+
401
+ You can take a look at the [Grape::OAuth2 mixins](https://github.com/nbulaj/grape_oauth2/tree/master/lib/grape_oauth2/mixins)
402
+ to understand what they are doing and what they are returning.
403
+
404
+ #### ResourceOwner
405
+
406
+ As was said before, Resource Owner class (`User` model for example) must contain only one class method
407
+ (**only for** Password Authorization Grant): `self.oauth_authenticate(client, username, password)`.
408
+
409
+ ```ruby
410
+ class User
411
+ # ...
412
+
413
+ def self.oauth_authenticate(client, username, password)
414
+ # Returns an instance of the User class with matching username
415
+ # and password. If there is no such User or password doesn't match
416
+ # then returns nil.
417
+ end
418
+ end
419
+ ```
420
+
421
+ ## Usage examples
422
+ ### I'm lazy, give me all out of the box!
423
+
424
+ If you need a common OAuth2 authentication then you can use default gem endpoints for it. First of all you
425
+ will need to configure Grape::OAuth2 as described above (create models, migrations, configure the gem).
426
+ For `ActiveRecord` it would be as follows:
427
+
428
+ ```ruby
429
+ # app/models/access_token.rb
430
+ class AccessToken < ApplicationRecord
431
+ include Grape::OAuth2::ActiveRecord::AccessToken
432
+ end
433
+
434
+ # app/models/application.rb
435
+ class Application < ApplicationRecord
436
+ include Grape::OAuth2::ActiveRecord::Client
437
+ end
438
+
439
+ # app/models/user.rb
440
+ class User < ApplicationRecord
441
+ has_secure_password
442
+
443
+ # Don't forget to setup this method for your Resource Owner model!
444
+ def self.oauth_authenticate(_client, username, password)
445
+ user = find_by(username: username)
446
+ return if user.nil?
447
+
448
+ user.authenticate(password)
449
+ end
450
+ end
451
+
452
+ # config/oauth2.rb
453
+ Grape::OAuth2.configure do |config|
454
+ # Classes for OAuth2 Roles
455
+ config.client_class_name = 'Application'
456
+ config.access_token_class_name = 'AccessToken'
457
+ config.resource_owner_class_name = 'User'
458
+ end
459
+ ```
460
+
461
+ And just inject `Grape::OAuth2` into your main API class:
462
+
463
+ ```ruby
464
+ # app/twitter.rb
465
+ module Twitter
466
+ class API < Grape::API
467
+ version 'v1', using: :path
468
+ format :json
469
+ prefix :api
470
+
471
+ # Mount all endpoints by default.
472
+ # You can define a custom one you want to use by providing them
473
+ # as an argument:
474
+ # include Grape::OAuth2.api :token, :authorize
475
+ #
476
+ include Grape::OAuth2.api
477
+
478
+ # mount any other endpoints
479
+ # ...
480
+ end
481
+ end
482
+ ```
483
+
484
+ The `include Grape::OAuth2.api` could be replaced with the next (as it does the same):
485
+
486
+
487
+ ```ruby
488
+ # app/twitter.rb
489
+ module Twitter
490
+ class API < Grape::API
491
+ version 'v1', using: :path
492
+ format :json
493
+ prefix :api
494
+
495
+ # Add OAuth2 helpers
496
+ helpers Grape::OAuth2::Helpers::AccessTokenHelpers
497
+
498
+ # Inject token authentication middleware
499
+ use *Grape::OAuth2.middleware
500
+
501
+ # Mount default Grape::OAuth2 Token endpoint
502
+ mount Grape::OAuth2::Endpoints::Token
503
+ # Mount default Grape::OAuth2 Authorization endpoint
504
+ mount Grape::OAuth2::Endpoints::Authorize
505
+
506
+ # mount any other endpoints
507
+ # ...
508
+ end
509
+ end
510
+ ```
511
+
512
+ And that is all! Use the next available routes to get the Access Token:
513
+
514
+ ```
515
+ POST /oauth/token
516
+ POST /oauth/revoke
517
+ ```
518
+
519
+ Now you can protect your endpoints with `access_token_required!` method:
520
+
521
+ ```ruby
522
+ module Twitter
523
+ module Endpoints
524
+ class Status < Grape::API
525
+ resources :status do
526
+ get do
527
+ # public resource, no scopes required
528
+ access_token_required!
529
+
530
+ present(:status, current_user.status)
531
+ end
532
+
533
+ post do
534
+ # requires 'write' scope to exist in Access Token
535
+ access_token_required! :write
536
+
537
+ status = current_user.statuses.create!(body: 'Hi man!')
538
+ present(:status, status, with: V1::Entities::Status)
539
+ end
540
+ end
541
+ end
542
+ end
543
+ end
544
+ ```
545
+
546
+ If you need to protect all the routes in the endpoint, but it's requires different scopes, than you can
547
+ add `access_token_required!` helper to the `before` filter and setup required scopes directly for the endpoints:
548
+
549
+ ```ruby
550
+ module Twitter
551
+ module Endpoints
552
+ class Status < Grape::API
553
+ before do
554
+ access_token_required!
555
+ end
556
+
557
+ resources :status do
558
+ # public endpoint - no scopes required
559
+ get do
560
+ present(:status, current_user.status)
561
+ end
562
+
563
+ # private endpoint - requires :write scope
564
+ put ':id', scopes: [:write] do
565
+ status = current_user.statuses.create!(body: 'Hi man!')
566
+ present(:status, status, with: V1::Entities::Status)
567
+ end
568
+ end
569
+ end
570
+ end
571
+ end
572
+ ```
573
+
574
+ ### Hey, I wanna control all the authentication process!
575
+ #### Override default mixins
576
+
577
+ If you need to do some special things (check if `key` starts with _'MyAPI'_ word for example) and don't want to
578
+ write your own authentication endpoints, then you can just override default authentication methods in models
579
+ (only if you are using gem mixins, in other cases you **MUST** write them by yourself):
580
+
581
+ ```ruby
582
+ # app/models/application.rb
583
+ class Application < ApplicationRecord
584
+ include Grape::OAuth2::ActiveRecord::Client
585
+
586
+ class << self
587
+ def self.authenticate(key, secret = nil)
588
+ # My custom condition for successful authentication
589
+ return nil unless key.start_with?('MyAPI')
590
+
591
+ if secret.present?
592
+ find_by(key: key, secret: secret)
593
+ else
594
+ find_by(key: key)
595
+ end
596
+ end
597
+ end
598
+ end
599
+ ```
600
+
601
+ #### Custom authentication endpoints
602
+
603
+ Besides, you can create your own API endpoints for OAuth2 authentication and use `grape_oauth2` gem functionality.
604
+ In that case you will get a full control over the authentication proccess and can do anything in it. Just create
605
+ a common Grape API class, set optional OAuth2 params and process the request with the `Grape::OAuth2::Generators::Token`
606
+ generator for example (for issuing an access token):
607
+
608
+ ```ruby
609
+ # api/oauth2.rb
610
+ module MyAPI
611
+ class OAuth2 < Grape::API
612
+ helpers Grape::OAuth2::Helpers::OAuthParams
613
+
614
+ namespace :oauth do
615
+ params do
616
+ use :oauth_token_params
617
+ end
618
+
619
+ post :token do
620
+ token_response = Grape::OAuth2::Generators::Token.generate_for(env) do |request, response|
621
+ # You can use default authentication if you don't need to change this part:
622
+ # client = Grape::OAuth2::Strategies::Base.authenticate_client(request)
623
+
624
+ # Or write your custom client authentication:
625
+ client = Application.find_by(key: request.client_id, active: true)
626
+ request.invalid_client! unless client
627
+
628
+ # You can use default Resource Owner authentication if you don't need to change this part:
629
+ # resource_owner = Grape::OAuth2::Strategies::Base.authenticate_resource_owner(client, request)
630
+
631
+ # Or define your custom resource owner authentication:
632
+ resource_owner = User.find_by(username: request.username)
633
+ request.invalid_grant! if resource_owner.nil? || resource_owner.inactive?
634
+
635
+ # You can create an Access Token as you want:
636
+ token = MyAwesomeAccessToken.create(client: client,
637
+ resource_owner: resource_owner,
638
+ scope: request.scope)
639
+
640
+ response.access_token = Grape::OAuth2::Strategies::Base.expose_to_bearer_token(token)
641
+ end
642
+
643
+ # If request is successful, then return it
644
+ status token_response.status
645
+
646
+ token_response.headers.each do |key, value|
647
+ header key, value
648
+ end
649
+
650
+ body token_response.access_token
651
+ end
652
+
653
+ desc 'OAuth 2.0 Token Revocation'
654
+
655
+ params do
656
+ use :oauth_token_revocation_params
657
+ end
658
+
659
+ post :revoke do
660
+ # ...
661
+ end
662
+ end
663
+ end
664
+ end
665
+ ```
666
+
667
+ ## Custom Access Token authenticator
668
+
669
+ If you don't want to use default `Grape::OAuth2` Access Token authenticator then you can define your own in the
670
+ configuration (it must be a `proc` or `lambda`):
671
+
672
+ ```ruby
673
+ Grape::OAuth2.configure do |config|
674
+ config.token_authenticator do |request|
675
+ AccessToken.find_by(token: request.access_token) || request.invalid_token!
676
+ end
677
+
678
+ # or config.token_authenticator = lambda { |request| ... }
679
+ end
680
+ ```
681
+
682
+ Don't forget to add the middleware to your root API class (`use *Grape::OAuth2.middleware`, see below).
683
+
684
+ ## Custom scopes validation
685
+
686
+ If you want to control the process of scopes validation (for protected endpoints for example) then you must implement
687
+ your own class that will implement the following API:
688
+
689
+ ```ruby
690
+ class CustomScopesValidator
691
+ # `scopes' is the set of required scopes that must be
692
+ # present in the Access Token instance.
693
+ def initialize(scopes)
694
+ @scopes = scopes || []
695
+ # ...some custom processing of scopes if required ...
696
+ end
697
+
698
+ def valid_for?(access_token)
699
+ # custom scopes validation implementation...
700
+ end
701
+ end
702
+ ```
703
+
704
+ And set that class as scopes validator in the Grape::OAuth2 config:
705
+
706
+ ```ruby
707
+ Grape::OAuth2.configure do |config|
708
+ # ...
709
+
710
+ config.scopes_validator_class_name = 'CustomScopesValidator'
711
+ end
712
+ ```
713
+
714
+ ## Custom token generator
715
+
716
+ If you want to generate your own tokens for Access Tokens and Authorization Codes then you need to write your own generator:
717
+
718
+ ```ruby
719
+ class SomeTokenGenerator
720
+ # @param payload [Hash]
721
+ # Access Token payload (attributes before creation for example)
722
+ #
723
+ # @param options [Hash]
724
+ # Options for Generator
725
+ #
726
+ def self.generate(payload = {}, options = {})
727
+ # Returns a generated token string.
728
+ end
729
+ end
730
+ ```
731
+
732
+ And set it as a token generator class in the Grape::OAuth2 config:
733
+
734
+ ```ruby
735
+ Grape::OAuth2.configure do |config|
736
+ # ...
737
+
738
+ config.token_generator_class_name = 'SomeTokenGenerator'
739
+ end
740
+ ```
741
+
742
+ ## Process token on Refresh (protect against Replay Attacks)
743
+
744
+ If you want to do something with the original Access Token that was used with the Refresh Token Flow, then you need to
745
+ setup `on_refresh` configuration option. By default `Grape::OAuth2` gem does nothing on token refresh and that
746
+ option is set to `:nothing`. You can set it to the symbol (in that case `Access Token` instance must respond to it)
747
+ or block. Look at the examples:
748
+
749
+ ```ruby
750
+ Grape::OAuth2.configure do |config|
751
+ # ...
752
+
753
+ config.on_refresh = :destroy # will call :destroy method (`refresh_token.destroy`)
754
+ end
755
+ ```
756
+
757
+ ```ruby
758
+ Grape::OAuth2.configure do |config|
759
+ # ...
760
+
761
+ config.on_refresh do |refresh_token|
762
+ refresh_token.destroy
763
+
764
+ MyAwesomeLogger.info("Token ##{refresh_token.id} was destroyed on refresh!")
765
+ end
766
+ end
767
+ ```
768
+
769
+ ## Errors (exceptions) handling
770
+
771
+ You can add any exception class from the [`rack-oauth2`](https://github.com/nov/rack-oauth2) gem (like `Rack::OAuth2::Server::Resource::Bearer::Unauthorized`)
772
+ to the `rescue_from` if you need to return some special response.
773
+
774
+ Example:
775
+
776
+ ```ruby
777
+ module Twitter
778
+ class API < Grape::API
779
+ include Grape::OAuth2.api
780
+
781
+ # ...
782
+
783
+ rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e|
784
+ error!({ status: e.status, description: e.description, error: e.error}, 400)
785
+ end
786
+ end
787
+ end
788
+ ```
789
+
790
+ Do not forget to meet the OAuth 2.0 specification.
791
+
792
+ ## Example App
793
+
794
+ If you want to see the gem in action then you can look at [sample app](https://github.com/grape-oauth2/grape-oauth2-sample) (deployable to Heroku).
795
+
796
+ Or you can take a look at the [sample applications](https://github.com/nbulaj/grape_oauth2/tree/master/spec/dummy) in the "_spec/dummy_" project directory.
797
+
798
+ ## Contributing
799
+
800
+ You are very welcome to help improve `grape_oauth2` if you have suggestions for features that other people can use.
801
+
802
+ To contribute:
803
+
804
+ 1. Fork the project.
805
+ 1. Create your feature branch (`git checkout -b my-new-feature`).
806
+ 1. Implement your feature or bug fix.
807
+ 1. Add documentation for your feature or bug fix.
808
+ 1. Add tests for your feature or bug fix.
809
+ 1. Run `rake` to make sure all tests pass.
810
+ 1. Commit your changes (`git commit -am 'Add new feature'`).
811
+ 1. Push to the branch (`git push origin my-new-feature`).
812
+ 1. Create new pull request.
813
+
814
+ Thanks.
815
+
816
+ ## License
817
+
818
+ `Grape::OAuth2` gem is released under the [MIT License](http://www.opensource.org/licenses/MIT).
819
+
820
+ Copyright (c) 2014-2016 Nikita Bulai (bulajnikita@gmail.com).