--- a/mod_http_oauth2/README.markdown Wed Oct 25 17:18:50 2023 +0200
+++ b/mod_http_oauth2/README.markdown Thu May 25 09:31:21 2023 +0200
@@ -51,6 +51,7 @@
- [RFC 7591: OAuth 2.0 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591.html)
- [RFC 7628: A Set of Simple Authentication and Security Layer (SASL) Mechanisms for OAuth](https://www.rfc-editor.org/rfc/rfc7628)
- [RFC 7636: Proof Key for Code Exchange by OAuth Public Clients](https://www.rfc-editor.org/rfc/rfc7636)
+- [RFC 7662: OAuth 2.0 Token Introspection](https://www.rfc-editor.org/rfc/rfc7662)
- [RFC 8628: OAuth 2.0 Device Authorization Grant](https://www.rfc-editor.org/rfc/rfc8628)
- [RFC 9207: OAuth 2.0 Authorization Server Issuer Identification](https://www.rfc-editor.org/rfc/rfc9207.html)
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
--- a/mod_http_oauth2/mod_http_oauth2.lua Wed Oct 25 17:18:50 2023 +0200
+++ b/mod_http_oauth2/mod_http_oauth2.lua Thu May 25 09:31:21 2023 +0200
@@ -138,6 +138,8 @@
return client;
end
+local purpose_map = { ["oauth2-refresh"] = "refresh_token"; ["oauth"] = "access_token" };
+
-- scope : string | array | set
--
-- at each step, allow the same or a subset of scopes
@@ -1047,6 +1049,47 @@
}
end
+local function handle_introspection_request(event)
+ local request = event.request;
+ local credentials = get_request_credentials(request);
+ if not credentials or credentials.type ~= "basic" then
+ event.response.headers.www_authenticate = string.format("Basic realm=%q", module.host.."/"..module.name);
+ return 401;
+ end
+ -- OAuth "client" credentials
+ if not verify_client_secret(credentials.username, credentials.password) then
+ return 401;
+ end
+
+ local form_data = http.formdecode(request.body or "=");
+ local token = form_data.token;
+ if not token then
+ return 400;
+ end
+
+ local token_info = tokens.get_token_info(form_data.token);
+ if not token_info then
+ return { headers = { content_type = "application/json" }; body = json.encode { active = false } };
+ end
+
+ return {
+ headers = { content_type = "application/json" };
+ body = json.encode {
+ active = true;
+ client_id = credentials.username; -- We don't really know for sure
+ username = jid.node(token_info.jid);
+ scope = token_info.grant.data.oauth2_scopes;
+ token_type = purpose_map[token_info.purpose];
+ exp = token.expires;
+ iat = token.created;
+ sub = url.build({ scheme = "xmpp"; path = token_info.jid });
+ aud = nil;
+ iss = get_issuer();
+ jti = token_info.id;
+ };
+ };
+end
+
local strict_auth_revoke = module:get_option_boolean("oauth2_require_auth_revoke", false);
local function handle_revocation_request(event)
@@ -1425,6 +1468,9 @@
-- Step 5. Revoke token (access or refresh)
["POST /revoke"] = handle_revocation_request;
+ -- Get info about a token
+ ["POST /introspect"] = handle_introspection_request;
+
-- OpenID
["GET /userinfo"] = handle_userinfo_request;
@@ -1446,6 +1492,7 @@
["GET /register"] = { headers = { content_type = "application/schema+json" }; body = json.encode(registration_schema) };
["GET /token"] = function() return 405; end;
["GET /revoke"] = function() return 405; end;
+ ["GET /introspect"] = function() return 405; end;
};
});
@@ -1482,6 +1529,8 @@
revocation_endpoint = handle_revocation_request and module:http_url() .. "/revoke" or nil;
revocation_endpoint_auth_methods_supported = array({ "client_secret_basic" });
device_authorization_endpoint = handle_device_authorization_request and module:http_url() .. "/device";
+ introspection_endpoint = handle_introspection_request and module:http_url() .. "/introspect";
+ introspection_endpoint_auth_methods_supported = nil;
code_challenge_methods_supported = array(it.keys(verifier_transforms));
grant_types_supported = array(it.keys(grant_type_handlers));
response_modes_supported = array(it.keys(response_type_handlers)):map(tmap { token = "fragment"; code = "query" });