Flybook is an enterprise-level collaborative office software of Bytedance. This article will introduce how to use Lua to implement the login authentication gateway of enterprise-level organization structure based on the authentication capability of Flybook open platform.

The login process

Let’s first take a look at the overall process of flying book third-party website exemption:

Step 1: The back end of the web page discovers that the user is not logged in and requests authentication. Step 2: After the user logs in, the open platform generates a login pre-authorization code, and the 302 is redirected to the address. Step 3: Obtain the login user identity through the web back end call, verify the validity of the login pre-authorization code, and obtain the user identity; Step 4: If additional user information is needed, the web back end can call to get user information (authentication).

Lua implementation

Part of the implementation of flying book interface

The access_token of the application is obtained

function _M:get_app_access_token(a)
    local url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
    local body = {
        app_id = self.app_id,
        app_secret = self.app_secret
    }
    local res, err = http_post(url, body, nil)
    if not res then
        return nil, err
    end
    if res.status~ =200 then
        return nil, res.body
    end
    local data = json.decode(res.body)
    if data["code"] ~ =0 then
        return nil, res.body
    end
    return data["tenant_access_token"]
end
Copy the code

Obtain the login user information through the callback code

function _M:get_login_user(code)
    local app_access_token, err = self:get_app_access_token()
    if not app_access_token then
        return nil."get app_access_token failed: ". errend
    local url = "https://open.feishu.cn/open-apis/authen/v1/access_token"
    local headers = {
        Authorization = "Bearer ". app_access_token }local body = {
        grant_type = "authorization_code",
        code = code
    }
    ngx.log(ngx.ERR, json.encode(body))
    local res, err = http_post(url, body, headers)
    if not res then
        return nil, err
    end
    local data = json.decode(res.body)
    if data["code"] ~ =0 then
        return nil, res.body
    end
    return data["data"]
end
Copy the code

Get user details

When you obtain user information, you cannot obtain user department information. Therefore, you need to use open_id in user information to obtain user details, and user_access_token is obtained from user information.

function _M:get_user(user_access_token, open_id)
    local url = "https://open.feishu.cn/open-apis/contact/v3/users/". open_idlocal headers = {
        Authorization = "Bearer ". user_access_token }local res, err = http_get(url, nil, headers)
    if not res then
        return nil, err
    end
    local data = json.decode(res.body)
    if data["code"] ~ =0 then
        return nil, res.body
    end
    return data["data"] ["user"].nil
end
Copy the code

The login information

JWT login credentials

We use JWT as the login credential, as well as the open_ID and department_IDS of the user.

- generate the token
function _M:sign_token(user)
    local open_id = user["open_id"]
    if not open_id or open_id == "" then
        return nil."invalid open_id"
    end
    local department_ids = user["department_ids"]
    if not department_ids or type(department_ids) ~= "table" then
        return nil."invalid department_ids"
    end

    return jwt:sign(
        self.jwt_secret,
        {
            header = {
                typ = "JWT",
                alg = jwt_header_alg,
                exp = ngx.time() + self.jwt_expire
            },
            payload = {
                open_id = open_id,
                department_ids = json.encode(department_ids)
            }
        }
    )
end

-- Validate and resolve tokens
function _M:verify_token(a)
    local token = ngx.var.cookie_feishu_auth_token
    if not token then
        return nil."token not found"
    end

    local result = jwt:verify(self.jwt_secret, token)
    ngx.log(ngx.ERR, "jwt_obj: ", json.encode(result))
    if result["valid"] then
        local payload = result["payload"]
        if payload["department_ids"] and payload["open_id"] then
            return payload
        end
        return nil."invalid token: ". json.encode(result)end
    return nil."invalid token: ". json.encode(result)end
Copy the code

Use cookies to store login credentials

ngx.header["Set-Cookie"] = self.cookie_key .. "=". tokenCopy the code

Whitelist of organizational structure

We obtain the department information of the user when the user logs in, or parse the department information in the login credentials when the user accesses the application, and determine whether the user has the permission to access the application according to the configured department whitelist.

- Configure the department whitelist
_M.department_whitelist = {}

function _M:check_user_access(user)
    if type(self.department_whitelist) ~= "table" then
        ngx.log(ngx.ERR, "department_whitelist is not a table")
        return false
    end
    if #self.department_whitelist == 0 then
        return true
    end

    local department_ids = user["department_ids"]
    if not department_ids or department_ids == "" then
        return false
    end
    if type(department_ids) ~= "table" then
        department_ids = json.decode(department_ids)
    end
    for i=1, #department_ids do
        if has_value(self.department_whitelist, department_ids[i]) then
            return true
        end
    end
    return false
end
Copy the code

More Gateway Configuration

Both IP blacklist and route whitelist can be configured.

-- IP blacklist configuration
_M.ip_blacklist = {}
Configure the route whitelist
_M.uri_whitelist = {}

function _M:auth(a)
    local request_uri = ngx.var.uri
    ngx.log(ngx.ERR, "request uri: ", request_uri)

    if has_value(self.uri_whitelist, request_uri) then
        ngx.log(ngx.ERR, "uri in whitelist: ", request_uri)
        return
    end

    local request_ip = ngx.var.remote_addr
    if has_value(self.ip_blacklist, request_ip) then
        ngx.log(ngx.ERR, "forbided ip: ", request_ip)
        return ngx.exit(ngx.HTTP_FORBIDDEN)
    end

    if request_uri == self.logout_uri then
        return self:logout()
    end

    local payload, err = self:verify_token()
    if payload then
        if self:check_user_access(payload) then
            return
        end

        ngx.log(ngx.ERR, "user access not permitted")
        self:clear_token()
        return self:sso()
    end
    ngx.log(ngx.ERR, "verify token failed: ", err)

    if request_uri ~= self.callback_uri then
        return self:sso()
    end
    return self:sso_callback()
end
Copy the code

use

I won’t cover the installation of OpenResty in this article, but refer to my other article, “Installing OpenResty with Source Code on Ubuntu.”

download

cd /path/to
git clone [email protected]:ledgetech/lua-resty-http.git
git clone [email protected]:SkyLothar/lua-resty-jwt.git
git clone [email protected]:k8scat/lua-resty-feishu-auth.git
Copy the code

configuration

lua_package_path "/path/to/lua-resty-feishu-auth/lib/? .lua; /path/to/lua-resty-jwt/lib/? .lua; /path/to/lua-resty-http/lib/? .lua; /path/to/lua-resty-redis/lib/? .lua; /path/to/lua-resty-redis-lock/lib/? .lua;;" ; server { access_by_lua_block { local feishu_auth = require "resty.feishu_auth" feishu_auth.app_id = "" feishu_auth.app_secret = "" feishu_auth.callback_uri = "/feishu_auth_callback" feishu_auth.logout_uri = "/feishu_auth_logout" feishu_auth.app_domain = "feishu-auth.example.com" feishu_auth.jwt_secret = "thisisjwtsecret" Feishu_auth. ip_blacklist = {"47.1.2.3"} feishu_auth.uri_whitelist = {"/"} feishu_auth.department_whitelist = {"0"} feishu_auth:auth() } }Copy the code

Configuration instructions

  • app_idUsed for setting up feishu enterprise self-built applicationsApp ID
  • app_secretUsed for setting up feishu enterprise self-built applicationsApp Secret
  • callback_uriThis parameter is used to set the callback address after login to Feishu Web page (you need to set the redirection URL in the security Settings of feishu enterprise self-built application).
  • logout_uriUsed to set the logout address
  • app_domainThis parameter is used to set the access domain name (which must be the same as the access domain name of the service).
  • jwt_secretUse to set JWT secret
  • ip_blacklistThis parameter is used to set the IP blacklist
  • uri_whitelistThis parameter is used to set the address whitelist. For example, login authentication is not required on the home page
  • department_whitelistUsed to set the department whitelist (string)

Application Permission Description

  • Obtain basic department information
  • Obtain information about the department organizational structure
  • Read the address book as an application
  • Obtain user organizational structure information
  • Obtain basic user information

Open source

This project has been completed and has been open source on GitHub: K8SCat/Lua-resty-Feishu – Auth. I hope everyone can start to point a Star to show the recognition and support of this project!