Class: Himari::AccessToken

Inherits:
Object
  • Object
show all
Includes:
TokenString
Defined in:
lib/himari/access_token.rb

Defined Under Namespace

Classes: Bearer

Instance Attribute Summary collapse

Attributes included from TokenString

#verification

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TokenString

#format, hash_secret, included, #magic_header, #secret, #secret_hash, #secret_hash_prev, #verify!, #verify_expiry!, #verify_secret!

Constructor Details

#initialize(handle:, client_id:, claims:, expiry:, scopes: [], session_handle: nil, secret: nil, secret_hash: nil) ⇒ AccessToken

Returns a new instance of AccessToken.



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/himari/access_token.rb', line 76

def initialize(handle:, client_id:, claims:, expiry:, scopes: [], session_handle: nil, secret: nil, secret_hash: nil)
  @handle = handle
  @client_id = client_id
  @claims = claims
  @scopes = scopes
  @session_handle = session_handle
  @expiry = expiry

  @secret = secret
  @secret_hash = secret_hash
  @secret_hash_prev = nil
  @verification = nil
end

Instance Attribute Details

#claimsObject (readonly)

Returns the value of attribute claims.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def claims
  @claims
end

#client_idObject (readonly)

Returns the value of attribute client_id.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def client_id
  @client_id
end

#expiryObject (readonly)

Returns the value of attribute expiry.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def expiry
  @expiry
end

#handleObject (readonly)

Returns the value of attribute handle.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def handle
  @handle
end

#scopesObject (readonly)

Returns the value of attribute scopes.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def scopes
  @scopes
end

#session_handleObject (readonly)

Returns the value of attribute session_handle.



90
91
92
# File 'lib/himari/access_token.rb', line 90

def session_handle
  @session_handle
end

Class Method Details

.default_lifetimeObject



26
27
28
# File 'lib/himari/access_token.rb', line 26

def self.default_lifetime
  3600
end

.from_authz(authz) ⇒ Object

Parameters:



66
67
68
69
70
71
72
73
74
# File 'lib/himari/access_token.rb', line 66

def self.from_authz(authz)
  make(
    client_id: authz.client_id,
    claims: authz.claims,
    scopes: authz.scopes,
    session_handle: authz.session_handle,
    lifetime: authz.lifetime.access_token,
  )
end

.magic_headerObject



22
23
24
# File 'lib/himari/access_token.rb', line 22

def self.magic_header
  'hmat'
end

.parse(str, signing_key_provider: nil) ⇒ Object

Parse a presented access token into its opaque Format (handle + secret) for verification against storage. Two on-the-wire shapes are accepted:

  • the opaque token “hmat.<handle>.<secret>” (TokenString format), or

  • an RFC 9068 JWT (Himari::AccessTokenJwt) carrying the opaque token in its hmat claim.

For a JWT, the signature is verified first (requires signing_key_provider to resolve the kid), then the embedded opaque token is returned so the caller validates the secret against storage exactly as for an opaque token. Any malformed/unverifiable JWT becomes TokenString::InvalidFormat so callers handle one failure type.

Parameters:



42
43
44
45
46
# File 'lib/himari/access_token.rb', line 42

def self.parse(str, signing_key_provider: nil)
  return TokenString::Format.parse(magic_header, str) if str.to_s.start_with?("#{magic_header}.")

  parse_jwt(str, signing_key_provider)
end

.parse_jwt(str, signing_key_provider) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/himari/access_token.rb', line 48

def self.parse_jwt(str, signing_key_provider)
  raise TokenString::InvalidFormat, 'signing keys are required to verify a JWT access token' unless signing_key_provider

  jwt = JSON::JWT.decode(str, :skip_verification)
  key = jwt.kid && signing_key_provider.find(id: jwt.kid)
  raise TokenString::InvalidFormat, 'unknown or missing signing key (kid)' unless key

  jwt.verify!(key.pkey)

  hmat = jwt[magic_header]
  raise TokenString::InvalidFormat, 'missing hmat claim' unless hmat.is_a?(String) && hmat.start_with?("#{magic_header}.")

  TokenString::Format.parse(magic_header, hmat)
rescue JSON::JWT::Exception, JSON::ParserError => e
  raise TokenString::InvalidFormat, "invalid JWT access token: #{e.class}"
end

Instance Method Details

#as_jsonObject



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/himari/access_token.rb', line 135

def as_json
  {
    handle: handle,
    secret_hash: secret_hash,
    client_id: client_id,
    claims: claims,
    scopes: scopes,
    session_handle: session_handle,
    expiry: expiry.to_i,
  }
end

#as_logObject



124
125
126
127
128
129
130
131
132
133
# File 'lib/himari/access_token.rb', line 124

def as_log
  {
    handle: handle,
    client_id: client_id,
    claims: claims,
    scopes: scopes,
    session_handle: session_handle,
    expiry: expiry,
  }
end

#to_bearer(token_string: format.to_s) ⇒ Object

Parameters:

  • token_string (String) (defaults to: format.to_s)

    the on-the-wire access token to deliver. Defaults to the opaque format; the token endpoint passes the RFC 9068 JWT when one was minted.



100
101
102
103
104
105
# File 'lib/himari/access_token.rb', line 100

def to_bearer(token_string: format.to_s)
  Bearer.new(
    access_token: token_string,
    expires_in: (expiry - Time.now.to_i).to_i,
  )
end

#to_jwt(signing_key:, issuer:, now: Time.now) ⇒ Object

Render this token as an RFC 9068 JWT (Himari::AccessTokenJwt). The opaque secret travels in the JWT’s hmat claim, so the token validates against storage the same way either form does. exp is tied to this token’s own expiry rather than recomputed, keeping both forms in sync.

Parameters:



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/himari/access_token.rb', line 112

def to_jwt(signing_key:, issuer:, now: Time.now)
  AccessTokenJwt.new(
    access: self,
    claims: claims,
    client_id: client_id,
    signing_key: signing_key,
    issuer: issuer,
    time: now,
    lifetime: expiry - now.to_i,
  ).to_jwt
end

#userinfoObject



92
93
94
95
96
# File 'lib/himari/access_token.rb', line 92

def userinfo
  claims.merge(
    aud: client_id,
  )
end