comparison mod_http_oauth2/mod_http_oauth2.lua @ 5279:2b858cccac8f

mod_http_oauth2: Add support for refresh tokens
author Matthew Wild <mwild1@gmail.com>
date Fri, 24 Mar 2023 14:29:07 +0000
parents d94dba396f9f
children eb482defd9b0
comparison
equal deleted inserted replaced
5278:d94dba396f9f 5279:2b858cccac8f
59 return resp; 59 return resp;
60 end 60 end
61 61
62 local tokens = module:depends("tokenauth"); 62 local tokens = module:depends("tokenauth");
63 63
64 local default_access_ttl = module:get_option_number("oauth2_access_token_ttl", 86400);
65 local default_refresh_ttl = module:get_option_number("oauth2_refresh_token_ttl", nil);
66
64 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration. 67 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration.
65 local registration_key = module:get_option_string("oauth2_registration_key"); 68 local registration_key = module:get_option_string("oauth2_registration_key");
66 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256"); 69 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256");
67 local registration_options = module:get_option("oauth2_registration_options", { default_ttl = 60 * 60 * 24 * 90 }); 70 local registration_options = module:get_option("oauth2_registration_options", { default_ttl = 60 * 60 * 24 * 90 });
68 71
150 -- client needs to be revoked 153 -- client needs to be revoked
151 local function client_subset(client) 154 local function client_subset(client)
152 return { name = client.client_name; uri = client.client_uri }; 155 return { name = client.client_name; uri = client.client_uri };
153 end 156 end
154 157
155 local function new_access_token(token_jid, role, scope, ttl, client, id_token) 158 local function new_access_token(token_jid, role, scope_string, client, id_token, refresh_token_info)
156 local token_data = {}; 159 local token_data = { oauth2_scopes = scope_string, oauth2_client = nil };
157 if client then 160 if client then
158 token_data.oauth2_client = client_subset(client); 161 token_data.oauth2_client = client_subset(client);
159 end 162 end
160 if next(token_data) == nil then 163 if next(token_data) == nil then
161 token_data = nil; 164 token_data = nil;
162 end 165 end
163 local token = tokens.create_jid_token(token_jid, token_jid, role, ttl, token_data, "oauth2"); 166
167 local refresh_token;
168 local access_token, access_token_info
169 -- No existing refresh token, and we're issuing a time-limited access token?
170 -- Create a refresh token (unless refresh_token_info == false)
171 if refresh_token_info == false or not default_access_ttl then
172 -- Caller does not want a refresh token, or access tokens are not configured to expire
173 -- So, just create a standalone access token
174 access_token, access_token_info = tokens.create_jid_token(token_jid, token_jid, role, default_access_ttl, token_data, "oauth2");
175 else
176 -- We're issuing both a refresh and an access token
177 if not refresh_token_info then
178 refresh_token, refresh_token_info = tokens.create_jid_token(token_jid, token_jid, role, default_refresh_ttl, token_data, "oauth2-refresh");
179 else
180 refresh_token = refresh_token_info.token;
181 end
182 access_token, access_token_info = tokens.create_sub_token(token_jid, refresh_token_info.id, role, default_access_ttl, token_data, "oauth2");
183 end
184 local expires_at = access_token_info.expires;
164 return { 185 return {
165 token_type = "bearer"; 186 token_type = "bearer";
166 access_token = token; 187 access_token = access_token;
167 expires_in = ttl; 188 expires_in = expires_at and (expires_at - os.time()) or nil;
168 scope = scope; 189 scope = scope_string;
169 id_token = id_token; 190 id_token = id_token;
170 -- TODO: include refresh_token when implemented 191 refresh_token = refresh_token;
171 }; 192 };
172 end 193 end
173 194
174 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string 195 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string
175 if not query_redirect_uri then 196 if not query_redirect_uri then
203 return oauth_error("invalid_grant", "incorrect credentials"); 224 return oauth_error("invalid_grant", "incorrect credentials");
204 end 225 end
205 226
206 local granted_jid = jid.join(request_username, request_host, request_resource); 227 local granted_jid = jid.join(request_username, request_host, request_resource);
207 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); 228 local granted_scopes, granted_role = filter_scopes(request_username, params.scope);
208 return json.encode(new_access_token(granted_jid, granted_role, granted_scopes, nil, nil)); 229 return json.encode(new_access_token(granted_jid, granted_role, granted_scopes, nil));
209 end 230 end
210 231
211 function response_type_handlers.code(client, params, granted_jid, id_token) 232 function response_type_handlers.code(client, params, granted_jid, id_token)
212 local request_username, request_host = jid.split(granted_jid); 233 local request_username, request_host = jid.split(granted_jid);
213 if not request_host or request_host ~= module.host then 234 if not request_host or request_host ~= module.host then
268 local request_username, request_host = jid.split(granted_jid); 289 local request_username, request_host = jid.split(granted_jid);
269 if not request_host or request_host ~= module.host then 290 if not request_host or request_host ~= module.host then
270 return oauth_error("invalid_request", "invalid JID"); 291 return oauth_error("invalid_request", "invalid JID");
271 end 292 end
272 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); 293 local granted_scopes, granted_role = filter_scopes(request_username, params.scope);
273 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, nil, client, nil); 294 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, client, nil);
274 295
275 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri)); 296 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri));
276 token_info.state = params.state; 297 token_info.state = params.state;
277 redirect.fragment = http.formencode(token_info); 298 redirect.fragment = http.formencode(token_info);
278 299
317 if not code or type(code) ~= "table" or code_expired(code) then 338 if not code or type(code) ~= "table" or code_expired(code) then
318 module:log("debug", "authorization_code invalid or expired: %q", code); 339 module:log("debug", "authorization_code invalid or expired: %q", code);
319 return oauth_error("invalid_client", "incorrect credentials"); 340 return oauth_error("invalid_client", "incorrect credentials");
320 end 341 end
321 342
322 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, nil, client, code.id_token)); 343 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token));
344 end
345
346 function grant_type_handlers.refresh_token(params)
347 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end
348 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end
349 if not params.refresh_token then return oauth_error("invalid_request", "missing 'refresh_token'"); end
350
351 local client_ok, client = jwt_verify(params.client_id);
352 if not client_ok then
353 return oauth_error("invalid_client", "incorrect credentials");
354 end
355
356 if not verify_client_secret(params.client_id, params.client_secret) then
357 module:log("debug", "client_secret mismatch");
358 return oauth_error("invalid_client", "incorrect credentials");
359 end
360
361 local refresh_token_info = tokens.get_token_info(params.refresh_token);
362 if not refresh_token_info or refresh_token_info.purpose ~= "oauth2-refresh" then
363 return oauth_error("invalid_grant", "invalid refresh token");
364 end
365
366 -- new_access_token() requires the actual token
367 refresh_token_info.token = params.refresh_token;
368
369 return json.encode(new_access_token(token_info.jid, token_info.role, token_info.data.oauth2_scopes, client, nil, token_info));
323 end 370 end
324 371
325 -- Used to issue/verify short-lived tokens for the authorization process below 372 -- Used to issue/verify short-lived tokens for the authorization process below
326 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 }); 373 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 });
327 374
457 location = redirect_uri; 504 location = redirect_uri;
458 }; 505 };
459 }; 506 };
460 end 507 end
461 508
462 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {"authorization_code", "password"}) 509 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {"authorization_code", "password", "refresh_token"})
463 for handler_type in pairs(grant_type_handlers) do 510 for handler_type in pairs(grant_type_handlers) do
464 if not allowed_grant_type_handlers:contains(handler_type) then 511 if not allowed_grant_type_handlers:contains(handler_type) then
465 module:log("debug", "Grant type %q disabled", handler_type); 512 module:log("debug", "Grant type %q disabled", handler_type);
466 grant_type_handlers[handler_type] = nil; 513 grant_type_handlers[handler_type] = nil;
467 else 514 else