rack-oauth2-server 2.5.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ 2012-03-27 Version 2.6.0
2
+
3
+ Adding support for JWT tokens in the "assertion" grant type (Brian Ploetz)
4
+
5
+
1
6
  2012-03-26 Version 2.5.1
2
7
 
3
8
  Issue #13: Fix bug in oauth2-server introduced by pull request #9 (Rafael Chacon)
data/README.md CHANGED
@@ -28,7 +28,7 @@ different database engine, send us a pull request.
28
28
  For Rails 2.3/3.0, `Rack::OAuth2::Server` automatically adds itself as middleware when required, but you do need to
29
29
  configure it from within `config/environment.rb` (or one of the specific environment files). For example:
30
30
 
31
- ```
31
+ ```ruby
32
32
  Rails::Initializer.run do |config|
33
33
  . . .
34
34
  config.after_initialize do
@@ -44,7 +44,7 @@ end
44
44
  For Sinatra and Padrino, first require `rack/oauth2/sinatra` and register `Rack::OAuth2::Sinatra` into your application.
45
45
  For example:
46
46
 
47
- ```
47
+ ```ruby
48
48
  require "rack/oauth2/sinatra"
49
49
 
50
50
  class MyApp < Sinatra::Base
@@ -88,7 +88,7 @@ The authenticator is a block that receives either two or four parameters. The f
88
88
  other two are the client identifier and scope. It authenticated, it returns an identity, otherwise it can return nil or
89
89
  false. For example:
90
90
 
91
- ```
91
+ ```ruby
92
92
  oauth.authenticator = lambda do |username, password|
93
93
  user = User.find_by_username(username)
94
94
  user.id if user && user.authenticated?(password)
@@ -117,7 +117,7 @@ the user back to the client application with a suitable error code.
117
117
 
118
118
  In Rails, the entire flow would look something like this:
119
119
 
120
- ```
120
+ ```ruby
121
121
  class OauthController < ApplicationController
122
122
  def authorize
123
123
  if current_user
@@ -143,7 +143,7 @@ cannot render anything, but can set the right response headers and return a stat
143
143
 
144
144
  In Sinatra/Padrino, it would look something like this:
145
145
 
146
- ```
146
+ ```ruby
147
147
  get "/oauth/authorize" do
148
148
  if current_user
149
149
  render "oauth/authorize"
@@ -207,7 +207,7 @@ header `oauth.no_scope` to the scope name, or using `oauth_required` with the sc
207
207
 
208
208
  In Rails, it would look something like this:
209
209
 
210
- ```
210
+ ```ruby
211
211
  class MyController < ApplicationController
212
212
 
213
213
  before_filter :set_current_user
@@ -244,7 +244,7 @@ end
244
244
 
245
245
  In Sinatra/Padrino, it would look something like this:
246
246
 
247
- ```
247
+ ```ruby
248
248
  before do
249
249
  @current_user = User.find(oauth.identity) if oauth.authenticated?
250
250
  end
@@ -309,7 +309,7 @@ Loading development environment (Rails 2.3.8)
309
309
  You may want your application to register its own client application, always with the same client ID and secret, which
310
310
  are also stored in a configuration file. For example, your `db/seed.rb` may contain:
311
311
 
312
- ```
312
+ ```ruby
313
313
  oauth2 = YAML.load_file(Rails.root + "config/oauth2.yml")
314
314
  Rack::OAuth2::Server.register(id: oauth2["client_id"], secret: oauth2["client_secret"],
315
315
  display_name: "UberClient", link: "http://example.com",
@@ -332,6 +332,41 @@ authorization process. This is typically used in server to server scenarios wher
332
332
  two-legged flow, send the grant_type of "none" along with the client_id and client_secret to the access token path, and
333
333
  a new access token will be generated (assuming the client_id and client_secret check out).
334
334
 
335
+ ## Assertions
336
+
337
+ Rack::OAuth2::Server supports the use of assertions (in the form of the `assertion` grant_type)
338
+ to obtain access tokens. Currently JSON Web Tokens (JWT) are the only supported assertion_type.
339
+
340
+ http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
341
+
342
+ http://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03
343
+
344
+ In order to verify the signatures of assertions, you will need to create assertion Issuers.
345
+ You can register assertion Issuers using the command line tool `oauth2-server`:
346
+
347
+ ```
348
+ $ oauth2-server register_issuer --db my_db
349
+ ```
350
+
351
+ Programatically, registering a new Issuer is as simple as:
352
+
353
+ ```
354
+ $ ./script/console
355
+ Loading development environment (Rails 2.3.8)
356
+ > issuer = Rack::OAuth2::Server.register_issuer(:identifier => "http://www.someidp.com",
357
+ :hmac_secret => "foo",
358
+ :public_key => "-----BEGIN RSA PUBLIC KEY-----\n....\n...=\n-----END RSA PUBLIC KEY-----\n")
359
+ ```
360
+
361
+ When you call `register_issuer` it either registers a new issuer with these specific values, or if an issuer already exists with the given
362
+ identifier it will update it's properties.
363
+
364
+ Depending on the algorithm used for signing the assertion (HMAC SHA or RSA), you pass either `:hmac_secret` or `:public_key` to `Rack::OAuth2::Server.register_issuer`
365
+ (or both if you will use both with a single issuer). The value of `:public_key` can be either a PEM or DER encoded public key (as supported by `OpenSSL::PKey::RSA.new`).
366
+
367
+ Rack::OAuth2::Server validates that the issuer (`iss`), principal (`prn`), audience (`aud`) and expiration (`exp`) claims are present. It also validates that the expiration
368
+ claim has not passed (with a 10 minute padding added to account for server clock skew).
369
+
335
370
 
336
371
  ## OAuth Web Admin
337
372
 
@@ -352,7 +387,7 @@ $ oauth2-server setup --db my_db
352
387
  Next, in your application, make sure to ONLY AUTHORIZE ADMINISTRATORS to access the Web admin, by granting them access
353
388
  to the `oauth-admin` scope. For example:
354
389
 
355
- ```
390
+ ```ruby
356
391
  def grant
357
392
  # Only admins allowed to authorize the scope oauth-admin
358
393
  if oauth.scope.include?("oauth-admin") && !current_user.admin?
@@ -368,14 +403,14 @@ Make sure you do that, or you'll allow anyone access to the OAuth Web admin.
368
403
  After this, remember to include the server admin module in your initializer (environemnt.rb or application.rb), because
369
404
  this is an optional feature:
370
405
 
371
- ```
406
+ ```ruby
372
407
  require "rack/oauth2/server/admin"
373
408
  ```
374
409
 
375
410
  Next, mount the OAuth Web admin as part of your application, and feed it the client ID/secret. For example, for Rails
376
411
  2.3.x add this to `config/environment.rb`:
377
412
 
378
- ```
413
+ ```ruby
379
414
  Rails::Initializer.run do |config|
380
415
  . . .
381
416
  config.after_initialize do
@@ -389,7 +424,7 @@ end
389
424
 
390
425
  For Rails 3.0.x, add this to you `config/application.rb`:
391
426
 
392
- ```
427
+ ```ruby
393
428
  module MyApp
394
429
  class Application < Rails::Application
395
430
  config.after_initialize do
@@ -403,11 +438,13 @@ For Rails 3.0.x, add this to you `config/application.rb`:
403
438
 
404
439
  And add the follownig to `config/routes.rb`:
405
440
 
441
+ ```ruby
406
442
  mount Rack::OAuth2::Server::Admin=>"/oauth/admin"
443
+ ```
407
444
 
408
445
  For Sinatra, Padrino and other Rack-based applications, you'll want to mount like so (e.g. in `config.ru`):
409
446
 
410
- ```
447
+ ```ruby
411
448
  Rack::Builder.new do
412
449
  map("/oauth/admin") { run Rack::OAuth2::Server::Admin }
413
450
  map("/") { run MyApp }
@@ -499,6 +536,18 @@ end up sending the access token to any server you `curl`. Useful for development
499
536
  production access tokens.
500
537
 
501
538
 
539
+ Example using the `assertion` grant_type:
540
+
541
+ ```
542
+ $ curl -i http://localhost:3000/oauth/access_token \
543
+ -F client_id=4dca20453e4859cb000007 \
544
+ -F client_secret=981fa734e110496fcf667cbf52fbaf03 \
545
+ -F grant_type=assertion \
546
+ -F assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer \
547
+ -F assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vd3d3LnNvbWVjb21wYW55LmNvbSIsImF1ZCI6Imh0dHA6Ly93d3cubXljb21wYW55LmNvbSIsInBybiI6IjEyMzQ1Njc4OTAifQ.bDrcogybtJ9n5d2a971Q72ye7GN64u7WXmr2OLxSeyc
548
+ ```
549
+
550
+
502
551
  ## Methods You'll Want To Use From Your App
503
552
 
504
553
  You can use the Server module to create, fetch and otherwise work with access tokens and grants. Available methods
@@ -516,7 +565,9 @@ include:
516
565
  client's ID and secret). Idempotent, so perfect for running during setup and migration.
517
566
  - `get_auth_request` -- Resolves authorization request handle into an `AuthRequest` object. Could be useful during the
518
567
  authorization flow.
519
-
568
+ - `register_issuer` -- Registers a new assertion issuer. Can also be used to change
569
+ existing issuers. Idempotent, so perfect for running during setup and migration.
570
+ - `get_issuer` -- Resolves an issuer identifier into an Issuer object.
520
571
 
521
572
  ## Mandatory ASCII Diagram
522
573
 
@@ -592,6 +643,11 @@ client).
592
643
  An `Rack::OAuth2::Server::AccessToken` is created by copying values from an `AuthRequest` or `AccessGrant`, and remains
593
644
  in effect until revoked. (OAuth 2.0 access tokens can also expire, but we don't support expiration at the moment)
594
645
 
646
+ ### Issuer
647
+
648
+ An issuer is a identity provider which issues assertions that may be used to
649
+ obtain an access token.
650
+
595
651
 
596
652
  ## Credits
597
653
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.5.1
1
+ 2.6.0
@@ -63,6 +63,24 @@ when "register"
63
63
  puts "ID\t#{client.id}"
64
64
  puts "Secret\t#{client.secret}"
65
65
 
66
+ when "register_issuer"
67
+ fail "No database. Use the --db option to tell us which database to use" unless Server.options.database
68
+ begin
69
+ print "Identifier (typically a URL):\t"
70
+ identifier = $stdin.gets
71
+ print "HMAC secret:\t"
72
+ hmac_secret = $stdin.gets
73
+ print "RSA public key:\t\t"
74
+ public_key = $stdin.gets
75
+ issuer = Server.register_issuer(:identifier => identifier, :hmac_secret => hmac_secret, :public_key => public_key)
76
+ rescue
77
+ puts "\nFailed to register issuer: #{$!}"
78
+ exit -1
79
+ end
80
+ puts "Registered Issuer #{issuer.identifier}"
81
+ puts "HMAC secret\t#{issuer.hmac_secret}"
82
+ puts "RSA public key\t#{issuer.public_key}"
83
+
66
84
  when "setup"
67
85
 
68
86
  fail "No database. Use the --db option to tell us which database to use" unless Server.options.database
@@ -204,6 +222,7 @@ Commands:
204
222
  migrate Run this when migrating from 1.x to 2.x
205
223
  practice Runs a dummy OAuth 2.0 server, use this to test your OAuth 2.0 client
206
224
  register Register a new client application
225
+ register_issuer Register a new assertion issuer
207
226
  setup Create new admin account and help you setup the OAuth Web console
208
227
 
209
228
  Options:
@@ -54,4 +54,4 @@ require "rack/oauth2/models/client"
54
54
  require "rack/oauth2/models/auth_request"
55
55
  require "rack/oauth2/models/access_grant"
56
56
  require "rack/oauth2/models/access_token"
57
-
57
+ require "rack/oauth2/models/issuer"
@@ -0,0 +1,58 @@
1
+ module Rack
2
+ module OAuth2
3
+ class Server
4
+ # A third party that issues assertions
5
+ # http://tools.ietf.org/html/draft-ietf-oauth-assertions-01#section-5.1
6
+ class Issuer
7
+ class << self
8
+
9
+ # returns the Issuer object for the given identifier
10
+ def from_identifier(identifier)
11
+ Server.new_instance self, collection.find_one({:_id=>identifier})
12
+ end
13
+
14
+ # Create a new Issuer.
15
+ def create(args)
16
+ fields = {}
17
+ [:hmac_secret, :public_key, :notes].each do |key|
18
+ fields[key] = args[key] if args.has_key?(key)
19
+ end
20
+ fields[:created_at] = Time.now.to_i
21
+ fields[:updated_at] = Time.now.to_i
22
+ fields[:_id] = args[:identifier]
23
+ collection.insert(fields, :safe=>true)
24
+ Server.new_instance self, fields
25
+ end
26
+
27
+
28
+ def collection
29
+ prefix = Server.options[:collection_prefix]
30
+ Server.database["#{prefix}.issuers"]
31
+ end
32
+ end
33
+
34
+ # The unique identifier of this Issuer. String or URI
35
+ attr_reader :_id
36
+ alias :identifier :_id
37
+ # shared secret used for verifying HMAC signatures
38
+ attr_reader :hmac_secret
39
+ # public key used for verifying RSA signatures
40
+ attr_reader :public_key
41
+ # notes about this Issuer
42
+ attr_reader :notes
43
+
44
+
45
+ def update(args)
46
+ fields = [:hmac_secret, :public_key, :notes].inject({}) {|h,k| v = args[k]; h[k] = v if v; h}
47
+ self.class.collection.update({:_id => identifier }, {:$set => fields})
48
+ self.class.from_identifier(identifier)
49
+ end
50
+
51
+ Server.create_indexes do
52
+ # Used to revoke all pending access grants when revoking client.
53
+ collection.create_index [[:identifier, Mongo::ASCENDING]]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -3,6 +3,7 @@ require "rack/oauth2/models"
3
3
  require "rack/oauth2/server/errors"
4
4
  require "rack/oauth2/server/utils"
5
5
  require "rack/oauth2/server/helper"
6
+ require "iconv"
6
7
 
7
8
 
8
9
  module Rack
@@ -119,8 +120,42 @@ module Rack
119
120
  AccessToken.from_identity(identity)
120
121
  end
121
122
 
123
+ # Registers and returns a new Issuer. Can also be used to update
124
+ # existing Issuer, by passing the identifier of an existing Issuer record.
125
+ # That way, your setup script can create a new client application and run
126
+ # repeatedly without fail.
127
+ #
128
+ # @param [Hash] args Arguments for registering Issuer
129
+ # @option args [String] :identifier Issuer identifier. Use this to update
130
+ # an existing Issuer
131
+ # @option args [String] :hmac_secret The HMAC secret for this Issuer
132
+ # @option args [String] :public_key The RSA public key (in PEM format) for this Issuer
133
+ # @option args [Array] :notes Free form text, for internal use.
134
+ #
135
+ # @example Registering new Issuer
136
+ # Server.register_issuer :hmac_secret=>"foo", :notes=>"Company A"
137
+ # @example Migration using configuration file
138
+ # config = YAML.load_file(Rails.root + "config/oauth.yml")
139
+ # Server.register_issuer config["id"],
140
+ # :hmac_secret=>"bar", :notes=>"Company A"
141
+ def register_issuer(args)
142
+ if args[:identifier] && (issuer = get_issuer(args[:identifier]))
143
+ issuer.update(args)
144
+ else
145
+ Issuer.create(args)
146
+ end
147
+ end
148
+
149
+ # Returns an Issuer from it's identifier.
150
+ #
151
+ # @param [String] identifier the Issuer's identifier
152
+ # @return [Issuer]
153
+ def get_issuer(identifier)
154
+ Issuer.from_identifier(identifier)
155
+ end
122
156
  end
123
157
 
158
+
124
159
  # Options are:
125
160
  # - :access_token_path -- Path for requesting access token. By convention
126
161
  # defaults to /oauth/access_token.
@@ -381,6 +416,17 @@ module Rack
381
416
  identity = options.authenticator.call(*args)
382
417
  raise InvalidGrantError, "Username/password do not match" unless identity
383
418
  access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
419
+ when "assertion"
420
+ # 4.1.3. Assertion
421
+ requested_scope = request.POST["scope"] ? Utils.normalize_scope(request.POST["scope"]) : client.scope
422
+ assertion_type, assertion = request.POST.values_at("assertion_type", "assertion")
423
+ raise InvalidGrantError, "Missing assertion_type/assertion" unless assertion_type && assertion
424
+ # TODO: Add other supported assertion types (i.e. SAML) here
425
+ raise InvalidGrantError, "Unsupported assertion_type" if assertion_type != "urn:ietf:params:oauth:grant-type:jwt-bearer"
426
+ if assertion_type == "urn:ietf:params:oauth:grant-type:jwt-bearer"
427
+ identity = process_jwt_assertion(assertion)
428
+ access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
429
+ end
384
430
  else
385
431
  raise UnsupportedGrantType
386
432
  end
@@ -436,6 +482,48 @@ module Rack
436
482
  return [401, { "WWW-Authenticate"=>challenge }, [error && error.message || ""]]
437
483
  end
438
484
 
485
+ # Processes a JWT assertion
486
+ def process_jwt_assertion(assertion)
487
+ begin
488
+ require 'jwt'
489
+ require 'json'
490
+ require 'openssl'
491
+ require 'time'
492
+ # JWT.decode only returns the claims. Gotta get the header ourselves
493
+ header = JSON.parse(JWT.base64url_decode(assertion.split('.')[0]))
494
+ algorithm = header['alg']
495
+ payload = JWT.decode(assertion, nil, false)
496
+
497
+ raise InvalidGrantError, "missing issuer claim" if !payload.has_key?('iss')
498
+
499
+ issuer_identifier = payload['iss']
500
+ issuer = Issuer.from_identifier(issuer_identifier)
501
+ raise InvalidGrantError, 'Invalid issuer' if issuer.nil?
502
+ if algorithm =~ /^HS/
503
+ validated_payload = JWT.decode(assertion, issuer.hmac_secret, true)
504
+ elsif algorithm =~ /^RS/
505
+ validated_payload = JWT.decode(assertion, OpenSSL::PKey::RSA.new(issuer.public_key), true)
506
+ end
507
+
508
+ raise InvalidGrantError, "missing principal claim" if !validated_payload.has_key?('prn')
509
+ raise InvalidGrantError, "missing audience claim" if !validated_payload.has_key?('aud')
510
+ raise InvalidGrantError, "missing expiration claim" if !validated_payload.has_key?('exp')
511
+
512
+ expires = validated_payload['exp'].to_i
513
+ # add a 10 minute fudge factor for clock skew between servers
514
+ skewed_expires_time = expires + (10 * 60)
515
+ now = Time.now.utc.to_i
516
+ raise InvalidGrantError, "expired claims" if skewed_expires_time <= now
517
+ principal = validated_payload['prn']
518
+ principal
519
+ rescue JWT::DecodeError => de
520
+ raise InvalidGrantError, de.message
521
+ rescue JSON::ParserError => pe
522
+ raise InvalidGrantError, "Invalid segment encoding"
523
+ end
524
+ end
525
+
526
+
439
527
  # Wraps Rack::Request to expose Basic and OAuth authentication
440
528
  # credentials.
441
529
  class OAuthRequest < Rack::Request
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency "bson_ext"
25
25
  spec.add_dependency "sinatra", "~>1.1"
26
26
  spec.add_dependency "json"
27
+ spec.add_dependency "jwt", "~>0.1.4"
27
28
  end
@@ -1,4 +1,6 @@
1
1
  require "test/setup"
2
+ require "jwt"
3
+ require "openssl"
2
4
 
3
5
 
4
6
  # 4. Obtaining an Access Token
@@ -93,6 +95,14 @@ class AccessGrantTest < Test::Unit::TestCase
93
95
  post "/oauth/access_token", params
94
96
  end
95
97
 
98
+ def request_with_assertion(assertion_type, assertion)
99
+ basic_authorize client.id, client.secret
100
+ params = { :grant_type=>"assertion", :scope=>"read write" }
101
+ params[:assertion_type] = assertion_type if assertion_type
102
+ params[:assertion] = assertion if assertion
103
+ post "/oauth/access_token", params
104
+ end
105
+
96
106
 
97
107
  # 4. Obtaining an Access Token
98
108
 
@@ -255,6 +265,125 @@ class AccessGrantTest < Test::Unit::TestCase
255
265
  teardown { config.authenticator = @old }
256
266
  end
257
267
 
268
+ # 4.1.3. Assertion
269
+
270
+ context "assertion" do
271
+ context "no assertion_type" do
272
+ setup { request_with_assertion nil, "myassertion" }
273
+ should_return_error :invalid_grant
274
+ end
275
+
276
+ context "no assertion" do
277
+ setup { request_with_assertion "urn:some:assertion:type", nil }
278
+ should_return_error :invalid_grant
279
+ end
280
+
281
+ context "unsupported assertion_type" do
282
+ setup { request_with_assertion "urn:some:assertion:type", "myassertion" }
283
+ should_return_error :invalid_grant
284
+ end
285
+
286
+ context "JWT" do
287
+ setup {
288
+ @hour_from_now = Time.now.utc.to_i + (60 * 60)
289
+ }
290
+ context "malformed assertion" do
291
+ setup { request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", "myassertion" }
292
+ should_return_error :invalid_grant
293
+ end
294
+
295
+ context "missing principal claim" do
296
+ setup {
297
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
298
+ @claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "exp" => @hour_from_now}
299
+ jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
300
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
301
+ }
302
+ should_return_error :invalid_grant
303
+ end
304
+
305
+ context "missing audience claim" do
306
+ setup {
307
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
308
+ @claims = {"iss" => @hmac_issuer.identifier, "prn" => "1234567890", "exp" => @hour_from_now}
309
+ jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
310
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
311
+ }
312
+ should_return_error :invalid_grant
313
+ end
314
+
315
+ context "missing expiration claim" do
316
+ setup {
317
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
318
+ @claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890"}
319
+ jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
320
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
321
+ }
322
+ should_return_error :invalid_grant
323
+ end
324
+
325
+ context "missing issuer claim" do
326
+ setup {
327
+ @claims = {"aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
328
+ jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
329
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
330
+ }
331
+ should_return_error :invalid_grant
332
+ end
333
+
334
+ context "unknown issuer" do
335
+ setup {
336
+ @claims = {"iss" => "unknown", "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
337
+ jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
338
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
339
+ }
340
+ should_return_error :invalid_grant
341
+ end
342
+
343
+ context "valid HMAC assertion" do
344
+ setup {
345
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
346
+ @claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
347
+ jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
348
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
349
+ }
350
+ should_respond_with_access_token "read write"
351
+ end
352
+
353
+ context "valid RSA assertion" do
354
+ setup {
355
+ @private_key = OpenSSL::PKey::RSA.generate(512)
356
+ @rsa_issuer = Server.register_issuer(:identifier => "http://www.rsaissuer.com", :public_key => @private_key.public_key.to_pem, :notes => "Test RSA Issuer")
357
+ @claims = {"iss" => @rsa_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
358
+ jwt_assertion = JWT.encode(@claims, @private_key, "RS256")
359
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
360
+ }
361
+ should_respond_with_access_token "read write"
362
+ end
363
+
364
+ context "expired claim set" do
365
+ setup {
366
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
367
+ @claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => Time.now.utc.to_i - (11 * 60)}
368
+ jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
369
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
370
+ }
371
+ should_return_error :invalid_grant
372
+ end
373
+
374
+ context "expiration claim within the fudge factor time" do
375
+ setup {
376
+ @hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
377
+ @claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => Time.now.utc.to_i - (9 * 60)}
378
+ jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
379
+ request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
380
+ }
381
+ should_respond_with_access_token "read write"
382
+ end
383
+
384
+ end
385
+ end
386
+
258
387
 
259
388
  # 4.2. Access Token Response
260
389