rack-oauth2-server 1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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.