Skip to content

Commit

Permalink
feat: Support of password grant type for token generation (apache#6574)
Browse files Browse the repository at this point in the history
Committing changes after adding new feature for generating token based on `user id/password` and also taking support of grant type `password`

feat: apache#6574
  • Loading branch information
Rushabh-Sukhadia committed Mar 11, 2022
1 parent 6645bbb commit 22c8d8e
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 5 deletions.
110 changes: 105 additions & 5 deletions apisix/plugins/authz-keycloak.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ local schema = {
access_token_expires_leeway = {type = "integer", minimum = 0, default = 0},
refresh_token_expires_in = {type = "integer", minimum = 1, default = 3600},
refresh_token_expires_leeway = {type = "integer", minimum = 0, default = 0},
},
token_userinfo_endpoint = {type = "string", minLength = 1, maxLength = 4096},
token_generation_endpoint = {type = "string", minLength = 1, maxLength = 4096},
include_user_info = {type = "boolean", default = false}

This comment has been minimized.

Copy link
@azilentech

azilentech Mar 11, 2022

Please don't commit user info code here.

},
allOf = {
-- Require discovery or token endpoint.
{
Expand Down Expand Up @@ -320,15 +323,15 @@ end

-- Return access_token expires_in value (in seconds).
local function authz_keycloak_access_token_expires_in(conf, expires_in)
return (expires_in or conf.access_token_expires_in)
- 1 - conf.access_token_expires_leeway
return (expires_in or conf.access_token_expires_in or 300)
- 1 - (conf.access_token_expires_leeway or 0)
end


-- Return refresh_token expires_in value (in seconds).
local function authz_keycloak_refresh_token_expires_in(conf, expires_in)
return (expires_in or conf.refresh_token_expires_in)
- 1 - conf.refresh_token_expires_leeway
return (expires_in or conf.refresh_token_expires_in or 3600)
- 1 - (conf.refresh_token_expires_leeway or 0)
end


Expand Down Expand Up @@ -694,9 +697,106 @@ local function fetch_jwt_token(ctx)
end
return token
end
-- This function is used to split data from array by respective delimitter and return array in result
local function stringsplit(s, delimiter)
local result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
return result;
end

-- To get new access token by calling get token api
local function generate_token(conf,ctx)
log.warn("------------Generate Access Token Function Called---------------")
--Read Body
ngx.req.read_body(); --To read requestbody first
--Get Body Data
local RequestBody=ngx.req.get_body_data();
local UserName = ""; local Password = "";
--split by &
local strBodyArr = stringsplit(RequestBody, "&");
if strBodyArr then
for k, strBodyValue in ipairs(strBodyArr) do
if string.find(strBodyValue, "username") then
--split by =
local usr = stringsplit(strBodyValue, "=");
UserName = usr[2];
end
if string.find(strBodyValue, "password") then
local psw = stringsplit(strBodyValue, "=");
Password = psw[2];
end
end
end

local client_id = authz_keycloak_get_client_id(conf)

local token_endpoint = authz_keycloak_get_token_endpoint(conf)

if not token_endpoint then
log.error("Unable to determine token endpoint.")
return 500, "Unable to determine token endpoint."
end
local httpc = authz_keycloak_get_http_client(conf)

local params = {
method = "POST",
body = ngx.encode_args({
grant_type = "password",
client_id = client_id,
client_secret = conf.client_secret,
username = UserName,
password = Password
}),
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
}

params = authz_keycloak_configure_params(params, conf)

local res, err = httpc:request_uri(token_endpoint, params)

if not res then
err = "Accessing token endpoint URL (" .. token_endpoint
.. ") failed: " .. err
log.error(err)
return 401, {message = err}
end

log.debug("Response data: " .. res.body)
local json, err = authz_keycloak_parse_json_response(res)

log.warn(core.json.encode(json))
if not json then
err = "Could not decode JSON from token endpoint"
.. (err and (": " .. err) or '.')
log.error(err)
return 401, {message = err}
end

return res.status, res.body;
end

function _M.access(conf, ctx)
-- Get Requested URI
local RequestURI = string.upper(ngx.var.request_uri);

if conf.token_generation_endpoint then
-- Get Token generation end point of key cloak which we have mession in keyclock plugin config
local token_generation_endpoint = string.upper(conf.token_generation_endpoint);
local curr_req_method = string.upper(ctx.curr_req_matched["_method"]);
--Call Generate Access Token function if "\Token" found in URI based on configuration
if RequestURI == token_generation_endpoint then
if curr_req_method ~= "POST" then
log.error("Invalid request type")
return 400, {message = "Request method must be POST only.!"}
end

return generate_token(conf,ctx);
end
end
log.debug("hit keycloak-auth access")
local jwt_token, err = fetch_jwt_token(ctx)
if not jwt_token then
Expand Down
21 changes: 21 additions & 0 deletions docs/en/latest/plugins/authz-keycloak.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/
| keepalive_timeout | integer | optional | 60000 | positive integer >= 1000 | Idle timeout after which established HTTP connections will be closed. |
| keepalive_pool | integer | optional | 5 | positive integer >= 1 | Maximum number of connections in the connection pool. |
| access_denied_redirect_uri | string | optional | | [1, 2048] | Redirect unauthorized user with the given uri like "http://127.0.0.1/test", instead of returning `"error_description":"not_authorized"`. |
| token_generation_endpoint | string | optional | | | Endpoint path to identify URL pattern based on Path configuration. So that we can generate token if Request Uri match with this path. |

### Discovery and Endpoints

Expand Down Expand Up @@ -122,6 +123,26 @@ of the same name. The scope is then added to every permission to check.
If `lazy_load_paths` is `false`, the plugin adds the mapped scope to any of the static permissions configured
in the `permissions` attribute, even if they contain one or more scopes already.

### Customized authentication and token generation by plugin configuration

Till now whenever new request came, keycloak plugin was first checking JWT token.
But from now if user don't have token then, we can generate new token with `token_endpoint` based on incomming Request URI path matching in `token_generation_endpoint`.
This will use to generate new JWT token. And for other route config, if will check token and redirect to the resource which are allocated to user. For this token generation, by default grant_type will be `password`.

## Customized authentication example

```cURL Code
curl --location --request POST 'http://127.0.0.1:9080/api/Token' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Origin: http://127.0.0.1:8080' \
--header 'Referer: 127.0.0.1:8080' \
--header 'client_secret: Client Secret' \
--header 'client_id: Client ID' \
--data-urlencode 'username=User Name' \
--data-urlencode 'password=Password'
```

## How To Enable

Create a `route` and enable the `authz-keycloak` plugin on the route:
Expand Down

0 comments on commit 22c8d8e

Please sign in to comment.