Class: Identizer::TokenMinter

Inherits:
Object
  • Object
show all
Defined in:
lib/identizer/token_minter.rb

Overview

Mints id_tokens and serves the OIDC discovery + JWKS documents.

Two signing modes:

:hs256 (default) — a shared symmetric key. Matches the original emulator's
  "consumers don't verify" behaviour; simplest for local dev.
:rs256 — an RSA keypair (persisted under config_dir) with a published JWKS,
  so real OIDC clients that DO verify signatures work out of the box.

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ TokenMinter

Returns a new instance of TokenMinter.



12
13
14
# File 'lib/identizer/token_minter.rb', line 12

def initialize(config)
  @config = config
end

Instance Method Details

#discoveryObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/identizer/token_minter.rb', line 47

def discovery
  base = @config.base_url
  {
    "issuer" => @config.issuer,
    "authorization_endpoint" => "#{base}/v1/authorize",
    "token_endpoint" => "#{base}/v1/token",
    "userinfo_endpoint" => "#{base}/userinfo",
    "jwks_uri" => "#{base}/.well-known/jwks.json",
    "introspection_endpoint" => "#{base}/introspect",
    "revocation_endpoint" => "#{base}/revoke",
    "end_session_endpoint" => "#{base}/v1/logout",
    "response_types_supported" => ["code"],
    "grant_types_supported" => %w[authorization_code refresh_token],
    "code_challenge_methods_supported" => %w[S256 plain],
    "subject_types_supported" => ["public"],
    "id_token_signing_alg_values_supported" => [@config.rs256? ? "RS256" : "HS256"],
    "scopes_supported" => %w[openid email profile]
  }
end

#id_token(identity, nonce: nil, audience: nil) ⇒ Object



16
17
18
19
20
21
22
23
# File 'lib/identizer/token_minter.rb', line 16

def id_token(identity, nonce: nil, audience: nil)
  payload = payload(identity, nonce: nonce, audience: audience)
  if @config.rs256?
    JWT.encode(payload, rsa_key, "RS256", { kid: jwk.kid })
  else
    JWT.encode(payload, @config.hs256_key, "HS256")
  end
end

#jwksObject



41
42
43
44
45
# File 'lib/identizer/token_minter.rb', line 41

def jwks
  return { "keys" => [] } unless @config.rs256?

  { "keys" => [jwk.export] }
end

#payload(identity, nonce: nil, audience: nil) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/identizer/token_minter.rb', line 25

def payload(identity, nonce: nil, audience: nil)
  now = Time.now.to_i
  registered = {
    "iss" => @config.issuer,
    # Audience is the requesting client_id when known, so OIDC clients that
    # validate `aud == client_id` accept the token; falls back to a constant.
    "aud" => audience.to_s.empty? ? "identizer" : audience,
    "iat" => now,
    "exp" => now + 3600 # id_token lifetime (intentionally separate from access_token_ttl)
  }
  registered["nonce"] = nonce unless nonce.to_s.empty?
  # Registered claims always win over identity-derived ones, so a directory
  # attribute can never forge iss/aud/exp/iat/nonce.
  identity.to_h.merge(registered)
end