# Code explanation

The provided [sample code](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/b411101a8db9b626c056c4945bfc9256_6AKzAKSTdY.zip) accomplishes the front-end and back-end handling of free login and authentication for a web app. This article mainly focuses on explaining the principles of implementing free login and authentication handling.

## Code structure

The following is the directory structure and some core code file descriptions for the sample code:

```
|web_app_quick_start
     |public
         |index.html         -- Front-end entry index.html
     |server
         |server_config.js   -- Server-side configuration file, mainly storing AppID and AppSecret, generated by npm run config
         |server_util.js     -- Server-side utility class
         |server.js          -- Core backend code: request handling, route registration, etc.
     |src
         |components         
             |useapi         -- Demo React component using JSAPI
             |userinfo       -- React component displaying user information
         |pages         
             |home           -- Home component
             |notfound       -- notfound page component
         |utils        
             |auth_access_util.js   -- Core handling of free login and authentication
         |config        
             |client_config.js      -- Front-end configuration file, mainly storing AppID and AppSecret, generated by npm run config

|App.css
         |App.js              -- App component, front-end route registration
         |index.css
         |index.js            -- Front-end entry JS file
     |package.json            -- Dependency configuration file
     |README.md               -- Documentation file
```

## Code flow

As the front-end and back-end of the application, this [sample code](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/b411101a8db9b626c056c4945bfc9256_6AKzAKSTdY.zip) implements free login and authentication handling. The specific processing flow is shown in the following diagram:

- **Free login flow**

First obtain the application authorization certificate [app_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token_internal), and then comprehensively obtain the user authorization certificate [user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/access_token/create).

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/190e8fffab7f3634fcd638b7b4465645_6aU3CFG0MX.png?height=654&lazyload=true&width=1163)

- **Authentication flow**

First, obtain the custom app credential [tenant_access_token](https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token_internal), and then use it to obtain [jsapi_ticket](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/h5_js_sdk/authorization). Next, calculate the signature, and finally perform authentication through window.h5sdk.config.

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/196be1a23c6714287a3adce0b164eff8_B6sibWSldO.png?height=814&lazyload=true&width=1304)

## Introduction to server.js

### Define server-side free login handling

```JavaScript
//Handle the free login request and return the user's user_access_token
async function getUserAccessToken(ctx) {
    console.log("\n-------------------[Access to the server without login processing BEGIN]-----------------------------")
    serverUtil.configAccessControl(ctx)
    console.log(`Step ① of accessing the server: Receive the front-end free login request`)
    const accessToken = ctx.session.userinfo
    const lkToken = ctx.cookies.get(LJ_TOKEN_KEY) || ''
    if (accessToken && accessToken.access_token && lkToken.length > 0 && accessToken.access_token == lkToken) {
        console.log("Access to the server Step ②: Obtain the user_access_token information from the Session, the user has logged in")
        ctx.body = serverUtil.okResponse(accessToken)
        console.log("-------------------[Access to the server without login processing END]-----------------------------\n")
        return
    }
    let code = ctx.query["code"] || ""
    console.log("Access to the service provider step ②: Obtain the login pre-authorization code code")
    if (code.length == 0) { //code does not exist
        ctx.body = serverUtil.failResponse("Login pre-authorization code is empty, please retry!!!")
        return
    }
    //【request】app_access_token：https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token_internal
    console.log("Step 3 of accessing the service provider: request the application authorization credential app_access_token according to the AppID and App Secret")
    const internalRes = await axios.post("https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal", {
        "app_id": serverConfig.config.appId,
        "app_secret": serverConfig.config.appSecret
    }, { headers: { "Content-Type": "application/json" } })
    if (!internalRes.data) {
        ctx.body = serverUtil.failResponse("app_access_token request error")
        return
    }
    if (internalRes.data.code != 0) { //Non-zero means failure
        ctx.body = serverUtil.failResponse(`app_access_token request error: ${internalRes.data.msg}`)
        return
    }
    console.log("Access to the service provider Step ④: Obtain the issued application authorization certificate app_access_token")
    const app_access_token = internalRes.data.app_access_token || ""
    console.log("Step ⑤ of accessing the service provider: request the user authorization credential user_access_token according to the login pre-authorization code code and app_access_token")
    //【request】user_access_token: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/access_token/create
    const authenv1Res = await axios.post("https://open.feishu.cn/open-apis/authen/v1/access_token", { "grant_type": "authorization_code", "code": code }, {
        headers: {
            "Content-Type": "application/json; charset=utf-8",
            "Authorization": "Bearer " + app_access_token
        }
    })
    if (!authenv1Res.data) {
        ctx.body = serverUtil.failResponse("access_toke request error")
        return
    }
    if (authenv1Res.data.code != 0) {  //Non-zero means failure
        ctx.body = serverUtil.failResponse(`access_toke request error: ${authenv1Res.data.msg}`)
        return
    }
    console.log("Access to the server step ⑥: Obtain the user_access_token of the issued user authorization code certificate, update it to the Session, and return it to the front end")
    const newAccessToken = authenv1Res.data.data
    if (newAccessToken) {
        ctx.session.userinfo = newAccessToken
        serverUtil.setCookie(ctx, LJ_TOKEN_KEY, newAccessToken.access_token || '')
    } else {
        serverUtil.setCookie(ctx, LJ_TOKEN_KEY, '')
    }
    ctx.body = serverUtil.okResponse(newAccessToken)
    console.log("-------------------[Access to the server without login processing END]-----------------------------\n")
}
```

#### Code explanations

- Lines 8 to 13 in the above code block demonstrate retrieving `user_access_token` from the Session and returning it early.

- Lines 53 to 59 in the above code block demonstrate obtaining a new `user_access_token` and updating it to the Session.

- Timeliness: The `user_access_token` in the user information has a certain period of validity, with a default maximum validity period is about 2 hours. Many server-side APIs (such as [Docs APIs](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-overview)) require `user_access_token` as one of the access credentials. If you need to call these APIs, the server-side Session should store `user_access_token` to ensure that it can be used directly within the validity period. If you want to refresh `user_access_token`, you can refer to [Refresh user access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authen/refresh_access_token).
**Optimization suggestion**: After obtaining the logged-in user information, you can use server-side session caching to avoid making unnecessary requests to the authentication center, which would cause unnecessary web page loading delays or failures due to exceeding the call frequency limit of server-side APIs.

### Define server-side authentication handling

```JavaScript
//Process the authentication parameter request and return the authentication parameter
async function getSignParameters(ctx) {
    console.log("\n-------------------[Access party server authentication processing BEGIN]-----------------------------")
    serverUtil.configAccessControl(ctx)
    console.log(`Access to the server step ①: Receive the front-end authentication request`)
    const url = ctx.query["url"] ||""
    const tickeString = ctx.cookies.get(LJ_JSTICKET_KEY) || ""
    if (tickeString.length > 0) {
        console.log(`Step ② of accessing the server: Obtain jsapi_ticket from Cookie, calculate JSAPI authentication parameters, and return`)
        const signParam = calculateSignParam(tickeString, url)
        ctx.body = serverUtil.okResponse(signParam)
        console.log("-------------------[Access party server authentication processing END]-----------------------------\n")
        return
    }
    console.log(`Step ② of accessing the server: jsapi_ticket is not detected, and the application authorization credential tenant_access_token is requested based on the AppID and App Secret`)
    //【request】tenant_access_token：https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token_internal
    const internalRes = await axios.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", {
        "app_id": serverConfig.config.appId,
        "app_secret": serverConfig.config.appSecret
    }, { headers: { "Content-Type": "application/json" } })
    if (!internalRes.data) {
        ctx.body = serverUtil.failResponse('tenant_access_token request error')
        return
    }
    if (internalRes.data.code != 0) {
        ctx.body = serverUtil.failResponse(`tenant_access_token request error: ${internalRes.data.msg}`)
        return
    }
    console.log(`Access to the service provider step ③: Obtain the issued self-built application authorization certificate tenant_access_token`)
    const tenant_access_token = internalRes.data.tenant_access_token ||""
    console.log(`Access to the server step ④: request JSAPI temporary authorization credentials`)
    //【request】jsapi_ticket：https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/h5_js_sdk/authorization
    const ticketRes = await axios.post("https://open.feishu.cn/open-apis/jssdk/ticket/get", {}, {
        headers: {
            "Content-Type": "application/json; charset=utf-8",
            "Authorization": "Bearer " + tenant_access_token
        }
    })
    if (!ticketRes.data) {
        ctx.body = serverUtil.failResponse('get jssdk ticket request error')
        return
    }
    if (ticketRes.data.code != 0) { //Non-zero means failure
        ctx.body = serverUtil.failResponse(`get jssdk ticket request error: ${ticketRes.data.msg}`)
        return
    }
    console.log(`Step ⑤ of accessing the server: Obtain the issued JSAPI temporary authorization certificate and update it to Cookie`)
    const newTicketString = ticketRes.data.data.ticket || ""
    if (newTicketString.length > 0) {
        serverUtil.setCookie(ctx, LJ_JSTICKET_KEY, newTicketString)
    }
    console.log(`Step ⑥ of accessing the server: Calculate the JSAPI authentication parameters and return them to the front end`)
    const signParam = calculateSignParam(newTicketString, url)
    ctx.body = serverUtil.okResponse(signParam)
    console.log("-------------------[Access party server authentication processing END]-----------------------------\n")
}
//Calculate authentication parameters
function calculateSignParam(tickeString, url) {
    const timestamp = (new Date()).getTime()
    const verifyStr = `jsapi_ticket=${tickeString}&noncestr=${serverConfig.config.signRandomString}&timestamp=${timestamp}&url=${url}`
    let signature = CryptoJS.SHA1(verifyStr).toString(CryptoJS.enc.Hex)
    const signParam = {
        "app_id": serverConfig.config.appId,
        "signature": signature,
        "noncestr": serverConfig.config.signRandomString,
        "timestamp": timestamp,
    }
    return signParam
}
```

#### Code explanations

- Lines 7 to 14 in the above code block demonstrate retrieving `jsapi_ticket` from the Cookie and calculating the authentication parameters early.

- Lines 48 to 51 in the above code block demonstrate obtaining a new `jsapi_ticket` and updating it to the Cookie.
Optimization suggestion: `jsapi_ticket` (JSAPI temporary authorization credential) has a validity period of 2 hours. Developers can cache `jsapi_ticket` (with a cache expiration time of 2 hours) to the Cookie. Within the validity period, caching `jsapi_ticket` can reduce frequent requests to the open platform for `jsapi_ticket` and improve the efficiency of background processing.

### API service registration

```JavaScript
router.get(serverConfig.config.getUserAccessTokenPath, getUserAccessToken)
router.get(serverConfig.config.getSignParametersPath, getSignParameters)
```

#### Code explanations

The above code uses the Koa framework of Node.js and the get() method provided by the koa-router middleware to handle two different HTTP GET requests: `getUserAccessToken` and `getSignParameters`.

- When a GET request is made to `serverConfig.config.getUserAccessTokenPath`, koa-router will call the `getUserAccessToken` function to handle the request.

- When a GET request is made to `serverConfig.config.getSignParametersPath`, koa-router will call the `getSignParameters` function to handle the request.

## Introduction to auth_access_util.js

### Define front-end free login handling

```JavaScript
/// ---------------- App Bind Part Core JS -------------------------
const LJ_TOKEN_KEY = 'lk_token'
//Handle user ban logic
export async function handleUserAuth(complete) {
    console.log("\n----------[Handling of non-login on the web page of the access BEGIN]----------")
    let lj_tokenString = Cookies.get(LJ_TOKEN_KEY) || ""
    if (lj_tokenString.length > 0) {
        console.log("The front end of the access party [Free login processing] Step ①: The user has logged in, and requests back-end verification...")
        requestUserAccessToken("", complete)
    } else {
        if (!window.h5sdk) {
            console.log('invalid h5sdk')
            complete()
            return
        }
        console.log("Front end of the access party [free login processing] step ①: call JSAPI tt.requestAuthCode according to the App ID to request the free login authorization code")
        //Call JSAPI tt.requestAuthCode according to the App ID to request the login pre-authorization code code
        window.h5sdk.ready(() => {
            console.log("window.h5sdk.ready");
            window.tt.requestAuthCode({
                appId: clientConfig.appId,
                success: (info) => {
                    const code = info.code
                    if (code.length <= 0) {
                        console.error('auth code is empty')
                        complete()
                    } else {
                        requestUserAccessToken(code, complete)
                    }
                },
                fail: (error) => {
                    complete()
                    console.error("window.tt.requestAuthCode", error)
                }
            });
        });
    }
}
function requestUserAccessToken(code, complete) {

// Get user_access_token information
    console.log("The front end of the access party [free login processing] Step ②: Go to the server of the access party to obtain user_access_token information")
    axios.get(`${getOrigin(clientConfig.apiPort)}${clientConfig.getUserAccessTokenPath}?code=${code}`,
        { withCredentials: true }   //When calling, set the request to bring the cookie
    ).then(function (response) {  // ignore_security_alert
        if (!response.data) {
            console.error(`${clientConfig.getUsee} response is null`)
            complete()
            return
        }
        const data = response.data.data
        if (data) {
            console.log("Front end of the access party [Free login processing] Step ③: Obtain user_access_token information")
            complete(data)
            localStorage.setItem(LJ_TOKEN_KEY, data.access_token)
            console.log("----------[Access to the web page is exempt from log-in processing END]----------\n")
        } else {
            console.error("Front-end of access side [Free login processing] step ③: user_access_token information not obtained")
            complete()
            console.log("----------[Access to the web page is exempt from log-in processing END]----------\n")
        }
    }).catch(function (error) {
        console.log(`${clientConfig.getSignParametersPath} error:`, error)
        complete()
        console.log("----------[Access to the web page is exempt from log-in processing END]----------\n")
    })
}
```

### Define JSAPI authentication handling

```JavaScript
/// ---------------- JSAPI authentication part -------------------------
export async function handleJSAPIAccess(complete) {
    console.log("\n----------[Access party web page JSAPI authentication processing BEGIN]----------")
    const url = encodeURIComponent(window.location.href.split("#")[0]);
    console.log("The front end of the access party [JSAPI authentication processing] Step ①: Request JSAPI authentication parameters")
    // Initiate a request to the access server to obtain authentication parameters（appId、timestamp、nonceStr、signature）
    const res = await axios.get(`${getOrigin(clientConfig.apiPort)}${clientConfig.getSignParametersPath}?url=${url}`,
        { withCredentials: true }
    )
    if (!res.data) {
        console.error(`${clientConfig.get_auth_parameters} fail`)
        complete(false)
        return
    }
    const data = res.data.data
    console.log("The front end of the access party [JSAPI authentication processing] Step ②: Obtain authentication parameters")
    if (!data) {
        console.error('Failed to get parameters')
        complete(false)
        return
    }
    console.log("The front end of the access party [JSAPI authentication processing] Step ③: Authenticate through window.h5sdk.config")
    configJSAPIAccess(data, complete)
}
//config JSAPI authentication
function configJSAPIAccess(data, complete) {
    //Configure the jsapi list to use
    let jsApiList = [
        "tt.getSystemInfo",
        "tt.showActionSheet",
        "tt.previewImage",
        "tt.showToast",
    ]
    // Call the config interface for authentication
    window.h5sdk.config({
        appId: data.app_id,
        timestamp: data.timestamp,
        nonceStr: data.noncestr,
        signature: data.signature,
        jsApiList: jsApiList,
        //success callback
        onSuccess: (res) => {
            console.log(`Authentication succeeded: ${JSON.stringify(res)}`);
            window.tt.showToast({
                title: "Authentication succeeded",
                icon: "success",
                duration: 2000
            });
            complete(true)
            console.log("\n----------[Access party webpage authentication processing END]----------")
        },
        //failure callback
        onFail: (err) => {
            window.tt.showToast({
                title: "Authentication failed",
                icon: "error",
                duration: 2000
            });
            complete(false)
            console.error(`Authentication failure reason: ${JSON.stringify(err)}`);
        },
    });
}
```

### Invoke authentication handling

```JavaScript
//Authentication processing
handleJSAPIAccess((isSucces) => {
   //...
})
```

### Invoke free login handling

```JavaScript
//Binding processing
handleUserAuth((userInfo) => {
    //...
})
```