changeset 5680:b43c989fb69c

mod_http_oauth2: Implement introspection endpoint "Tell me about this token"
author Kim Alvefur <zash@zash.se>
date Thu, 25 May 2023 09:31:21 +0200
parents e274431bf4ce
children 8cb3da7df521
files mod_http_oauth2/README.markdown mod_http_oauth2/mod_http_oauth2.lua
diffstat 2 files changed, 50 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- 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" });