rack-oauth2-server 1.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ 2010-11-02 1.0.0.beta
2
+
3
+ World premiere.
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ group :development do
5
+ gem "yard"
6
+ end
7
+
8
+ group :test do
9
+ gem "actionpack", "~>2.3"
10
+ gem "awesome_print"
11
+ gem "json"
12
+ gem "rack-test"
13
+ gem "rails", "~>2.3"
14
+ gem "shoulda"
15
+ gem "sinatra"
16
+ gem "timecop"
17
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Flowtown, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.rdoc ADDED
@@ -0,0 +1,423 @@
1
+ = Rack::OAuth2::Server
2
+
3
+ OAuth 2.0 Authorization Server as a Rack module. Because you don't allow
4
+ strangers into your app, and OAuth 2.0 is the new awesome.
5
+
6
+ http://tools.ietf.org/html/draft-ietf-oauth-v2-10
7
+
8
+
9
+ == Step 1: Setup Your Database
10
+
11
+ The authorization server needs to keep track of clients, authorization
12
+ requests, access grants and access tokens. That could only mean one thing: a
13
+ database.
14
+
15
+ The current release uses MongoDB[http://www.mongodb.org/]. You're going to need
16
+ a running server and open connection in the form of a +Mongo::DB+ object.
17
+ Because MongoDB is schema-less, there's no need to run migrations.
18
+
19
+ If MongoDB is not your flavor, you can easily change the models to support a
20
+ different database engine. All the persistence logic is located in
21
+ +lib/rack/oauth2/models+ and kept simple by design. And if you did the work to
22
+ support a different database engine, send us a pull request.
23
+
24
+
25
+ == Step 2: Use The Server
26
+
27
+ For Rails 2.3.x, you can require +rack/oauth2/rails+ and configure the server
28
+ from +config/environment.rb+. For example:
29
+
30
+ require "rack/oauth2/rails"
31
+
32
+ Rails::Initializer.run do |config|
33
+ config.oauth[:database] = Mongo::Connection.new["my_db"]
34
+ config.oauth[:scopes] = %w{read write}
35
+ config.oauth[:authenticator] = lambda do |username, password|
36
+ user = User.find(username)
37
+ user if user && user.authenticated?(password)
38
+ end
39
+
40
+ . . .
41
+ end
42
+
43
+ For Sinatra and Padrino, you can require +rack/oauth2/sinatra+ and register
44
+ {Rack::OAuth2::Sinatra} into your application. For example:
45
+
46
+ require "rack/oauth2/sinatra"
47
+
48
+ class MyApp < Sinatra::Base
49
+ register Rack::OAuth2::Sinatra
50
+
51
+ oauth[:database] = Mongo::Connection.new["my_db"]
52
+ oauth[:scopes] = %w{read write}
53
+ oauth[:authenticator] = lambda do |username, password|
54
+ user = User.find(username)
55
+ user if user && user.authenticated?(password)
56
+ end
57
+
58
+ . . .
59
+ end
60
+
61
+ With any other Rack server, you can +use Rack::OAuth2::Server+ with a hash of
62
+ configuration options.
63
+
64
+ The configuration options are:
65
+
66
+ - +:access_token_path+ -- Path for requesting access token. By convention
67
+ defaults to +/oauth/access_token+.
68
+ - +:authenticator+ -- For username/password authorization. A block that
69
+ receives the credentials and returns resource string (e.g. user ID) or nil.
70
+ - +:authorization_types+ -- Array of supported authorization types. Defaults to
71
+ ["code", "token"], and you can change it to just one of these names.
72
+ - +:authorize_path+ -- Path for requesting end-user authorization. By
73
+ convention defaults to +/oauth/authorize+.
74
+ - +:database+ -- +Mongo::DB+ instance.
75
+ - +:realm+ -- Authorization realm that will show up in 401 responses. Defaults
76
+ to use the request host name.
77
+ - +:scopes+ -- Array listing all supported scopes, e.g. ["read", "write"].
78
+ - +:logger+ -- The logger to use. Under Rails, defaults to use the Rails
79
+ logger. Will use +Rack::Logger+ if available.
80
+
81
+ If you only intend to use the UI authorization flow, you don't need to worry
82
+ about the authenticator. If you want to allow clients applications to create
83
+ access tokens by passing the end-user's username/password, then you need an
84
+ authenticator. This feature is necessary for some client applications, and
85
+ quite handy during development/testing.
86
+
87
+
88
+ == Step 3: Let Users Authorize
89
+
90
+ Authorization requests go to +/oauth/authorize+. {Rack::OAuth2::Server}
91
+ intercepts these requests and validates the client ID, secret, redirect URI,
92
+ authorization type and scope. If the request fails validation, the user is
93
+ redirected back to the client application with a suitable error code.
94
+
95
+ If the request passes validation, {Rack::OAuth2::Server} sets the request
96
+ header +oauth.authorization+ to the authorization handle, and passes control to
97
+ your application. Your application will ask the user to grant or deny the
98
+ authorization request.
99
+
100
+ Once granted, your application signals the grant by setting the response header
101
+ +oauth.authorization+ to the authorization handle it got before, and setting
102
+ the response header +oauth.resource+ to the authorized resource. This is
103
+ typicaly the user ID or account ID, but can be anything you want, as long as
104
+ it's a string. {Rack::OAuth2::Server} intercepts this response and redirects
105
+ the user back to the client application with an authorization code or access
106
+ token.
107
+
108
+ To signal that the user denied the authorization requests your application sets
109
+ the response header oauth.authorization as before, and returns the status code
110
+ 401 (Unauthorized). {Rack::OAuth2::Server} will then redirect the user back to
111
+ the client application with a suitable error code.
112
+
113
+ In Rails, the entire flow would look something like this:
114
+
115
+ class OauthController < ApplicationController
116
+ def authorize
117
+ @authorization = oauth.authorization
118
+ @client = oauth.client
119
+ @scope = oauth.scope
120
+ end
121
+
122
+ def grant
123
+ head oauth.grant!(params[:authorization], current_user.id)
124
+ end
125
+
126
+ def deny
127
+ head oauth.deny!(params[:authorization])
128
+ end
129
+ end
130
+
131
+ Rails actions must render something. The oauth method returns a helper object
132
+ ({Rack::OAuth2::Server::Helper}) that cannot render anything, but can set the right
133
+ response headers and return a status code, which we then pass on to the +head+
134
+ method.
135
+
136
+ In Sinatra/Padrino, it would look something like this:
137
+
138
+ get "/oauth/authorize" do
139
+ @authorization = oauth.authorization
140
+ @client = oauth.client
141
+ @scope = oauth.scope
142
+ render "oauth/authorize"
143
+ end
144
+
145
+ post "/oauth/grant" do
146
+ oauth.grant! params[:authorization], "Superman"
147
+ end
148
+
149
+ post "/oauth/deny" do
150
+ oauth.deny! params[:authorization]
151
+ end
152
+
153
+ The view would look something like this:
154
+
155
+ <h2>The application <% link_to h(@client.display_name), @client.link %>
156
+ is requesting to <%= @scope.to_sentence %> your account.</h2>
157
+ <form action="/oauth/grant">
158
+ <button>Grant</button>
159
+ <input type="hidden" name="authorization" value="<%= @authorization %>">
160
+ </form>
161
+ <form action="/oauth/deny">
162
+ <button>Deny</button>
163
+ <input type="hidden" name="authorization" value="<%= @authorization %>">
164
+ </form>
165
+
166
+
167
+ == Step 4: Protect Your Path
168
+
169
+ {Rack::OAuth2::Server} intercepts all incoming requests and looks for either
170
+ OAuth authentication header, or +oauth_token+ parameter. If it finds either
171
+ one, and the access token is still valid, it sets the request header
172
+ +oauth.resource+ to the value you supplied during authorization (step 3).
173
+
174
+ You can use +oauth.resource+ to resolve the access token back to user, account
175
+ or whatever you put there.
176
+
177
+ If the access token is invalid or revoked, it returns 401 (Unauthorized) to the
178
+ client. However, if there's no access token, the request goes through. You
179
+ might want to protect some URLs but not others, or allow authenticated and
180
+ unauthenticated access, the former returning more data or having higher rate
181
+ limit, etc.
182
+
183
+ It is up to you to reject requests that must be authenticated but are not. You
184
+ can always just return status code 401, but it's better to include a proper
185
+ +WWW-Authenticate+ header, which you can do by setting the response header
186
+ +oauth.no_access+ to true, or using +oauth_required+ to setup a filter.
187
+
188
+ You may also want to reject requests that don't have the proper scope. You can
189
+ return status code 403, but again it's better to include a proper
190
+ +WWW-Authenticate+ header with the required scope. You can do that by setting
191
+ the response header +oauth.no_scope+ to the scope name, or using
192
+ +oauth_required+ with the scope option.
193
+
194
+ In Rails, it would look something like this:
195
+
196
+ class MyController < ApplicationController
197
+
198
+ before_filter :set_current_user
199
+ oauth_required :only=>:private
200
+ oauth_required :only=>:calc, :scope=>"math"
201
+
202
+ # Authenticated/un-authenticated get different responses.
203
+ def public
204
+ if oauth.authenticated?
205
+ render :action=>"more-details"
206
+ else
207
+ render :action=>"less-details"
208
+ end
209
+ end
210
+
211
+ # Must authenticate to retrieve this.
212
+ def private
213
+ render
214
+ end
215
+
216
+ # Must authenticate with scope math to do this.
217
+ def calc
218
+ render :text=>"2+2=4"
219
+ end
220
+
221
+ protected
222
+
223
+ def set_current_user
224
+ @current_user = User.find(oauth.resource) if oauth.authenticated?
225
+ end
226
+
227
+ end
228
+
229
+ In Sinatra/Padrino, it would look something like this:
230
+
231
+ before do
232
+ @current_user = User.find(oauth.resource) if oauth.authenticated?
233
+ end
234
+
235
+ oauth_required "/private"
236
+ oauth_required "/calc", :scope=>"math"
237
+
238
+ # Authenticated/un-authenticated get different responses.
239
+ get "/public" do
240
+ if oauth.authenticated?
241
+ render "more-details"
242
+ else
243
+ render "less-details"
244
+ end
245
+ end
246
+
247
+ # Must authenticate to retrieve this.
248
+ get "/private" do
249
+ render "secrets"
250
+ end
251
+
252
+ # Must authenticate with scope math to do this.
253
+ get "/calc" do
254
+ render "2 + 2 = 4"
255
+ end
256
+
257
+
258
+ == Step 5: Register Some Clients
259
+
260
+ Before a client application can request access, there must be a client record
261
+ in the database. Registration provides the client application with a client
262
+ ID and secret. The client uses these to authenticate itself.
263
+
264
+ The client provides its display name, site URL and image URL. These should be
265
+ shown to the end-user to let them know which client application they're
266
+ granting access to.
267
+
268
+ Clients can also register a redirect URL. This is optional but highly
269
+ recommended for better security, preventing other applications from hijackin
270
+ the client's ID/secret.
271
+
272
+ For example:
273
+
274
+ $ ./script/console
275
+ Loading development environment (Rails 2.3.8)
276
+ > uber_client = Rack::OAuth2::Server::Client.create(:display_name=>"UberClient",
277
+ :link=>"http://uberclient.dot/",
278
+ :image_url=>"http://farm5.static.flickr.com/4122/4890273282_58f7c345f4.jpg",
279
+ :redirect_uri=>"http://uberclient.dot/oauth/callback")
280
+ > puts "Your client identifier: #{client.id}"
281
+ > puts "Your client secret: #{client.secret}"
282
+
283
+
284
+ == Step 6: Pimp Your API
285
+
286
+ I'll let you figure that one for yourself.
287
+
288
+
289
+ == Using With Curl
290
+
291
+ The premise of OAuth 2.0 is that you can use it straight from the command line.
292
+ Let's start by creating an access token. Aside from the UI authorization flow,
293
+ OAuth 2.0 allows you to authenticate with username/password. You'll need to
294
+ register an authenticator, see step 2 above for details.
295
+
296
+ Now make a request using the client credentials and your account
297
+ username/password, e.g.:
298
+
299
+ $ curl -i http://localhost:3000/oauth/access_token \
300
+ -F grant_type=password \
301
+ -F client_id=4dca20453e4859cb000007 \
302
+ -F client_secret=981fa734e110496fcf667cbf52fbaf03 \
303
+ -F "scope=read write" \
304
+ -F username=assaf@labnotes.org \
305
+ -F password=not.telling
306
+
307
+ This will spit out a JSON document, something like this:
308
+
309
+ {"scope":"import discover contacts lists",
310
+ "access_token":"e57807eb99f8c29f60a27a75a80fec6e"}
311
+
312
+ Grab the +access_token+ value and use it. The access token is good until you
313
+ delete it from the database. Making a request using the access token:
314
+
315
+ $ curl -i http://localhost:3000/api/read -H "Authorization: OAuth e57807eb99f8c29f60a27a75a80fec6e"
316
+
317
+ Although not recommended, you can also pass the token as a query parameter, or
318
+ when making POST request, as a form field:
319
+
320
+ $ curl -i http://localhost:3000/api/read?oauth_token=e57807eb99f8c29f60a27a75a80fec6e
321
+ $ curl -i http://localhost:3000/api/update -F name=Superman -F oauth_token=e57807eb99f8c29f60a27a75a80fec6e
322
+
323
+ Here's a neat trick. You can create a +.curlrc+ file and load it using the +-K+ option:
324
+
325
+ $ cat .curlrc
326
+ header = "Authorization: OAuth e57807eb99f8c29f60a27a75a80fec6e"
327
+ $ curl -i http://localhost:3000/api/read -K .curlrc
328
+
329
+ If you create +.curlrc+ in your home directory, +curl+ will automatically load it.
330
+ Convenient, but dangerous, you might end up sending the access token to any
331
+ server you +curl+. Useful for development, testing, just don't use it with any
332
+ production access tokens.
333
+
334
+
335
+ == Mandatory ASCII Diagram
336
+
337
+ This is briefly what the authorization flow looks like, how the workload is
338
+ split between {Rack::OAuth2::Server} and your application, and the protocol the
339
+ two use to control the authorization flow:
340
+
341
+ Rack::OAuth2::Server
342
+ ----------------------- -----------------------
343
+ Client app | /oauth/authorize | | Set request.env |
344
+ redirect -> | | -> | | ->
345
+ | authenticate client | | oauth.authorization |
346
+ ----------------------- -----------------------
347
+
348
+ Your code
349
+ -------------------- ---------------------- -----------------------
350
+ | Authenticate user | | Ask user to grant/ | | Set response |
351
+ -> | | -> | deny client access | -> | | ->
352
+ | | | to their account | | oauth.authorization |
353
+ | | | | | oauth.resource |
354
+ -------------------- ---------------------- -----------------------
355
+
356
+ Rack::OAuth2::Server
357
+ -----------------------
358
+ | Create access grant |
359
+ -> | or access token for | -> Redirect back
360
+ | oauth.resource | to client app
361
+ -----------------------
362
+
363
+
364
+ == Understanding the Models
365
+
366
+ === Client
367
+
368
+ The {Rack::OAuth2::Server::Client} model represents the credentials of a client
369
+ application. There are two pairs: the client identifier and secret, which the
370
+ client uses to identify itself to the authorization server, and the display
371
+ name and URL, which the client uses to identify itself to the end user.
372
+
373
+ The client application is not tied to a single Client record. Specifically, if
374
+ the client credentials are compromised, you'll want to revoke it and create a
375
+ new Client with new pair of identifier/secret. You can leave the revoked
376
+ instance around.
377
+
378
+ Calling +revoke!+ on the client revokes access using these credential pair, and
379
+ also revokes any outstanding authorization requests, access grants and access
380
+ tokens created using these credentials.
381
+
382
+ You may also want to register a redirect URI. If registered, the client is only
383
+ able to request authorization that redirect back to that redirect URI.
384
+
385
+ === Authorization Request
386
+
387
+ The authorization process may involve multiple requests, and the application
388
+ must maintain the authorization request details from beginning to end.
389
+
390
+ To keep the application simple, all the necessary information for a single
391
+ authorization request is stored in the {Rack::OAuth2::Server::AuthRequest}
392
+ model. The application only needs to keep track of the authorization request
393
+ identifier.
394
+
395
+ Granting an authorization request (by calling +grant!+) creates an access grant or
396
+ access token, depending on the requested response type, and associates it with
397
+ the resource.
398
+
399
+ === Access Grant
400
+
401
+ An access grant ({Rack::OAuth2::Server::AccessGrant}) is a nonce use to
402
+ generate access token. This model keeps track of the nonce (the "authorization
403
+ code") and all the data it needs to create an access token.
404
+
405
+ === Access Token
406
+
407
+ An access token allows the client to access a resource with the given scope. It
408
+ keeps track of the account identifier (supplied by the application), client
409
+ identifier and scope (both supplied by the client).
410
+
411
+ An {Rack::OAuth2::Server::AccessToken} is created by copying values from an
412
+ +AuthRequest+ or +AccessGrant+, and remains in effect until revoked. (OAuth 2.0
413
+ access tokens can also expire, but we don't support expiration at the moment)
414
+
415
+
416
+ == Credits
417
+
418
+ {Rack::OAuth2::Server} was written to provide authorization/authentication for
419
+ the new Flowtown API[http://developer.flowtown.com]. Thanks to
420
+ Flowtown[http://flowtown.com] for making it happen and allowing it to be open
421
+ sourced.
422
+
423
+ {Rack::OAuth2::Server} is available under the MIT license.