Commit a255f98
Changed files (2)
bin
test
bin/idp
@@ -105,7 +105,7 @@ module Authn
end
def create_access_token
- ::Authz::JWT.new(sub: to_global_id.to_s, iat: Time.now.to_i)
+ ::Authz::JWT.new(sub: to_global_id.to_s)
end
def assertion_attributes_for(request)
@@ -323,10 +323,30 @@ module Authz
end
class JWT
+ class << self
+ # TODO:: validate signature
+ def decode(encoded)
+ _header, body, _signature = encoded
+ .split('.', 3)
+ .map { |x| JSON.parse(Base64.strict_decode64(x), symbolize_names: true) rescue {} }
+ new(body)
+ end
+ end
+
attr_reader :claims
def initialize(claims)
- @claims = claims
+ now = Time.now.to_i
+ @claims = {
+ iat: now,
+ nbf: now,
+ jti: SecureRandom.uuid,
+ }.merge(claims)
+ end
+
+ def active?
+ # TODO:: check if current time is within valid range
+ true
end
def to_jwt
@@ -360,21 +380,14 @@ module Authz
false
end
- def subject_of(token)
- _header, claims, _signature = from_jwt(token)
- claims[:sub]
+ def subject_of(encoded_token)
+ token = ::Authz::JWT.decode(encoded_token)
+ token&.claims[:sub]
end
def resource_from(global_id)
GlobalID::Locator.locate(global_id)
end
-
- # TODO:: validate signature
- def from_jwt(token)
- token
- .split('.', 3)
- .map { |x| JSON.parse(Base64.strict_decode64(x), symbolize_names: true) rescue {} }
- end
end
end
@@ -499,6 +512,9 @@ module Authz
case request.path
when "/oauth/authorize" # RFC-6749
return post_authorize(request)
+ when "/oauth/introspect" # RFC-7662
+ params = request.content_type == "application/json" ? JSON.parse(request.body.read, symbolize_names: true) : Hash[URI.decode_www_form(request.body.read)].transform_keys(&:to_sym)
+ return post_introspect(params.slice(:token, :token_type_hint))
when "/oauth/token" # RFC-6749
params = request.content_type == "application/json" ? JSON.parse(request.body.read, symbolize_names: true) : Hash[URI.decode_www_form(request.body.read)].transform_keys(&:to_sym)
grant = AuthorizationGrant.find_by(params)
@@ -515,6 +531,13 @@ module Authz
http_not_found
end
+ private
+
+ def post_introspect(params)
+ token = ::Authz::JWT.decode(params[:token])
+ return [200, { "Content-Type" => "application/json" }, [JSON.pretty_generate(token.claims.merge(active: token.active?))]]
+ end
+
def get_authorize(oauth_params)
template = <<~ERB
<!DOCTYPE html>
test/e2e_test.go
@@ -277,6 +277,19 @@ func TestAuthx(t *testing.T) {
project := x.Must(serde.FromJSON[map[string]string](response.Body))
assert.Equal(t, "foo", project["name"])
})
+
+ t.Run("token can be introspected", func(t *testing.T) {
+ client := conf.Client(ctx, credentials)
+
+ io := bytes.NewBuffer(nil)
+ assert.NoError(t, serde.ToJSON(io, map[string]string{"token": credentials.AccessToken}))
+ response := x.Must(client.Post("http://idp.example.com:8080/oauth/introspect", "application/json", io))
+ require.Equal(t, http.StatusOK, response.StatusCode)
+
+ claims := x.Must(serde.FromJSON[map[string]interface{}](response.Body))
+ assert.Equal(t, true, claims["active"])
+ assert.Equal(t, "gid://example/Authn::User/1", claims["sub"])
+ })
})
})
}