Tin học 88 - chia sẻ kiến thức
ADS

Code Zalo API PHP Lấy access token để có quyền gọi Zalo API

Sau quá trình tạo và cấu hình ứng dụng, để có thể gọi các API của Zalo thì trước hết ứng dụng của bạn cần phải được Zalo cấp một cái “chìa khóa” đó là Access Token.

Mục lục

    Zalo có hai loại access token, trong đó Official Account Access Token sử dụng cho các dịch vụ liên quan đến Official Account (OA, Article, Shop, ZNS API) và User Access Token dành cho tài khoản cá nhân (Social API).

    - Zalo Login v4

    Việc lấy access token cho Official Account và User là tương tự nhau, chỉ khác URL tới Zalo Service.

    Official Account Access Token

    Luồng lấy oAuth access token cho Official Account như sau:

    Luồng lấy Access Token truy cập Official Account API (oAuth v4)

    User Access Token

    Luồng lấy oAuth access token cho User giống như Official Account nhưng khác ở URL đến Zalo Authentication Service.

    Luồng lấy Access Token truy cập Social API (oAuth v4)

    Trong oAuth v4, ta cần trải qua 2 bước sau đó là lấy Authorization Code rồi sau đó mới lấy được Access Token.

    Lấy Authorization Code

    Trước tiên sẽ ta tạo cặp mã code verifier (ngẫu nhiên) và code challenge để phục vụ cho phương thức PKCE, kèm theo một giá trị trạng thái ngẫu nhiên state để ngăn ngừa tấn công CSRF.

    Giá trị code verifier và state sẽ còn được sử dụng thêm 01 lần nữa trong auth.php ngay sau đó, vì vậy ta sẽ lưu tạm chúng vào PHP session.

    Từ index.php redirect tới Permission URL dưới đây để yêu cầu quyền truy cập Official Account.

    Loại API Permission URL
    Official Account API
    Article API
    Shop API
    ZNS API
    https://oauth.zaloapp.com/v4/oa/permission
    Social API https://oauth.zaloapp.com/v4/permission

    Tham số truyền vào query string cho URL nói trên gồm:

    Parameter Ý nghĩa
    app_id ID của ứng dụng
    redirect_uri Đường dẫn được cấu hình tại phần setting của Official Account đang liên kết với Ứng dụng của bạn. Ở đây là URL đầy đủ tới auth.php.
    code_challenge Mã code challenge đã tạo trong index.php từ code verifier phục vụ cho phương thức PKCE.
    Sẽ được trả về nguyên vẹn cho auth.php.
    state Mã trạng thái state đã tạo trong index.php với mục đích ngăn tấn công CSRF.
    Sẽ được trả về nguyên vẹn cho auth.php.

    Ở đây giả sử ta sẽ lưu access token trong một biến PHP session là zalo_access_token, sau đó sẽ được lưu phía client trong một biến như ví dụ dưới đây là initialToken.

    Bạn có thể lưu ở cookie hay local storage tùy theo thiết kế chương trình nhưng hãy thận trọng với XSS.

    <?php
    /**
     * index.php
     */
    define( "APP_ID", "1234567890123456789" ); // FILL your Application ID here!!!
    define( "APP_AUTH_URL", "https://zalo.example.com/auth.php" ); // URL to auth.php
    define( "ZALO_PERMISSION_URL", "https://oauth.zaloapp.com/v4/oa/permission" ); // OA, Article, Shop, ZNS API
    //define( "ZALO_PERMISSION_URL", "https://oauth.zaloapp.com/v4/permission" ); // Social API
    session_start();
    ///// Utility functions /////
    function base64url_encode($text) {
        $base64 = base64_encode($text);
        $base64 = trim($base64, "=");
        $base64url = strtr($base64, "+/", "-_");
        return $base64url;
    }
    function generate_state_param() {
        // a random 8 digit hex, for instance
        return bin2hex(openssl_random_pseudo_bytes(4));
    }
    function generate_pkce_codes() {
        $random = bin2hex(openssl_random_pseudo_bytes(32)); // a random 64-digit hex
        $code_verifier = base64url_encode(pack('H*', $random));
        $code_challenge = base64url_encode(pack('H*', hash('sha256', $code_verifier)));
        return array(
            "verifier" => $code_verifier,
            "challenge" => $code_challenge
        );
    }
    ///// Authentication Process /////
    if ( !isset($_SESSION["zalo_access_token"]) ) :
        $state = generate_state_param(); // for CSRF prevention
        // Generate the code verifier and code challenge
        $codes = generate_pkce_codes();
        // Store the request state to be checked in auth.php
        $_SESSION["zalo_auth_state"] = $state;
        // Store the code verifier to be used in auth.php
        $_SESSION["zalo_code_verifier"] = $codes["verifier"];
        $auth_uri = ZALO_PERMISSION_URL . "?" . http_build_query( array(
                         "app_id" => APP_ID,
                         "redirect_uri" => APP_AUTH_URL,
                         "code_challenge" => $codes["challenge"],
                         "state" => $state, // <- prevent CSRF
                     ) );
        header("Location: {$auth_uri}");
        exit;
    endif;
    // Store the INITIAL access token in a JavaScript constant.
    echo "<script>const initialToken = " . json_encode( $_SESSION["zalo_access_token"] ) . ";</script>\r\n";
    // Remove the access token in PHP session
    unset( $_SESSION["zalo_access_token"] );
    /***********************************
     *       START THE MAIN APP        *
     ***********************************/
    ?>

    Sau khi xác nhận quyền truy cập Official Account từ người dùng, Zalo sẽ redirect về auth.php, đồng thời truyền các tham số sau cho auth.php qua method GET.

    Parameter Ý nghĩa
    oa_id ID của Official Account đã cấp quyền cho Ứng dụng
    code  Authorization code mà ta sẽ dùng để xác thực trong bước tiếp theo.
    Code chỉ có hiệu lực trong vòng 10 phút.
    code_challenge Code challenge được trả lại.
    state Mã trạng thái được trả lại.

    Lấy Access Token

    auth.php gửi Authentication code và Code verifier đến Access Token URL dưới đây để yêu cầu tạo access token qua method POST:

    Loại API Access Token URL
    Official Account API, Article API, Shop API, ZNS API https://oauth.zaloapp.com/v4/oa/access_token
    Social API https://oauth.zaloapp.com/v4/access_token

    Tham số trong HTTP header:

    HTTP Header Ý nghĩa
    Content-Type application/x-www-form-urlencoded để xác định dữ liệu trong POST request là kiểu query string.
    secret_key Khóa bí mật của ứng dụng lấy từ trang cấu hình ứng dụng của Zalo.

    Tham số trong POST parameter:

    Parameter Ý nghĩa
    app_id ID của ứng dụng lấy từ trang cấu hình ứng dụng của Zalo.
    code  Authorization code mà ta đã lấy được ở bước trước đó.
    grant_type Giá trị: authorization_code – tạo access token từ authorization code.
    code_verifier Code verifier đã tạo trong index.php.

    Zalo platform sẽ trả về dữ liệu kiểu JSON bao gồm 3 thông tin sau:

    Parameter Ý nghĩa
    access_token Mã truy cập API của Zalo platform (access token).
    Hiệu lực trong vòng 1 giờ.
    refresh_token Mã để cấp lại access token mới khi đáo hạn (sau 25 giờ = 90,000 giây).
    Hiệu lực trong vòng 3 tháng.
    Có thể dùng hoặc không tùy thiết kế ứng dụng.
    expires_in Thời gian hiệu lực của access token = 90000.

    Bạn hãy lưu lại mã access_token để truy cập đến API của Zalo ví dụ ở đây ta sẽ lưu vào một HTTP only cookie là zalo_access_token.

    <?php
    /**
     * auth.php
     */
    define( "APP_SECRET", "012abcXYZ345defUVW78" ); // FILL your Application Secret Key here!!!
    define( "ZALO_ACCESS_TOKEN_URL", "https://oauth.zaloapp.com/v4/oa/access_token" ); // OA, Article, Shop, ZNS API
    //define( "ZALO_ACCESS_TOKEN_URL", "https://oauth.zaloapp.com/v4/access_token" ); // Social API
    session_start();
    // CSRF prevention
    $is_valid = isset($_REQUEST["state"]) && isset($_SESSION["zalo_auth_state"])
            && $_SESSION["zalo_auth_state"] == $_REQUEST["state"];
    if ( $is_valid ) :
        // Obtain the Access Token by performing a POST request to the Access Token URL
        $data = http_build_query( array(
                    "app_id" => APP_ID,
                    "code" => $_REQUEST["code"],
                    "code_verifier" => $_SESSION["zalo_code_verifier"],
                    "grant_type" => "authorization_code"
            ) );
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => ZALO_ACCESS_TOKEN_URL,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_HTTPHEADER => array(
                    "Content-Type: application/x-www-form-urlencoded",
                    "secret_key: " . APP_SECRET
                ),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_POSTFIELDS => $data,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_FAILONERROR => true,
        ) );
        $response = curl_exec($curl);
        curl_close($curl);
        $auth = json_decode( $response, true );
        // store the Access Token in a temporary PHP session variable
        $_SESSION["zalo_access_token"] = array(
            "access_token" => $auth["access_token"],
            "expires_in"   => $auth["expires_in"]
        );
        // store the Refresh Token in a secured HTTP only cookie
        $expr = time() + (3*30*24*60*60); // 3 months?
        setcookie( "zalo_refresh_token", $auth["refresh_token"],
                $expr, "/refresh",
                $_SERVER['HTTP_HOST'], true, true );
        // clean up the one-time-use state variables
        unset( $_SESSION["zalo_auth_state"] );
        unset( $_SESSION["zalo_code_verifier"] );
        // Go back to index.php
        header("Location: /index.php");
        exit;
    endif;
    // Otherwise response an error
    http_response_code(400);
    die('Bad Request');
    ?>

    Tạo access token mới khi hết hạn

    Do access token có thời gian sống khoảng 25 giờ (90,000 giây) nên ứng dụng cần yêu cầu cấp phát một access token mới mà không phải thông qua nhiều bước như trên. Đây là lý do refresh token tồn tại.

    Trong auth.php, refresh token đang được lưu trong một HTTP only cookie là zalo_refresh_token.

    Khi một request bị hết hạn, Zalo sẽ trả về JSON có chứa mã lỗi (-216 / Access token không hợp lệ). Khi đó, từ phía client có thể request trang refresh.php dưới đây để lấy access token mới và lưu trữ lại.

    Kết quả trả về dưới định dạng JSON để có thể sử dụng với JavaScript.

    <?php
    /**
     * refresh.php
     */
    define( "APP_ID", "1234567890123456789" ); // FILL your Application ID here!!!
    define( "APP_SECRET", "012abcXYZ345defUVW78" ); // FILL your Application Secret Key here!!!
    define( "ZALO_ACCESS_TOKEN_URL", "https://oauth.zaloapp.com/v4/oa/access_token" ); // OA, Article, Shop, ZNS API
    //define( "ZALO_ACCESS_TOKEN_URL", "https://oauth.zaloapp.com/v4/access_token" ); // Social API
    if ( isset($_COOKIE["zalo_refresh_token"]) ) :
        // Obtain the Access Token by performing a POST request to the Access Token URL
        $data = http_build_query( array(
                    "app_id" => APP_ID,
                    "refresh_token" => $_COOKIE["zalo_refresh_token"],
                    "grant_type" => "refresh_token"
            ) );
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => ZALO_ACCESS_TOKEN_URL,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_HTTPHEADER => array(
                    "Content-Type: application/x-www-form-urlencoded",
                    "secret_key: " . APP_SECRET
                ),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_POSTFIELDS => $data,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_FAILONERROR => true,
        ) );
        $response = curl_exec($curl);
        curl_close($curl);
        $auth = json_decode( $response, true );
        // store the new Refresh Token in a secured HTTP only cookie
        $expr = time() + (3*30*24*60*60); // 3 months?
        setcookie( "zalo_refresh_token", $auth["refresh_token"], $expr, "/refresh",
                $_SERVER['HTTP_HOST'], true, true );
        
        $result = array(
            "error" => 0,
            "message" => "Success",
            "access_token" => $auth["access_token"],
            "expires_in" =>  $auth["expires_in"],
        );
    else :
        $result = array(
            "error" => -1,
            "message" => "Refresh token not found",
        );
    endif;
    header( "Content-type: application/json; charset=utf-8" );
    echo json_encode( $result );
    ?>

    Để tự động dùng refresh token mỗi khi access token bị hết hạn mà không làm gián đoạn các request song song, bạn có thể sử dụng một framework JavaScript ajax ví dụ như axios với chức năng interceptors chẳng hạn.

     

     





    Bài viết khác

    Các loại Zalo API

    Có 5 loại ứng dụng Zalo tương ứng với những API khác nhau. Bài viết này sẽ trình bày sơ

    Các loại Zalo API

    Zalo API - PHP Thiết kế Web Service để tương tác với Zalo

    Thiết kế một Web Service trung gian giữa Client và Zalo viết bằng PHP. Đây là thiết kế cơ

    Zalo API - PHP Thiết kế Web Service để tương tác với Zalo

    Zalo API - PHP Xử lý sự kiện Webhook

    xây dựng một bộ khung cho Webhook và Callback để nhận các sự kiện của Official Account đã

    Zalo API - PHP Xử lý sự kiện Webhook

    Webhook là gì? Kiến thức cơ bản về Webhook

    Webhook là gì? Webhook là một tính năng cho phép website tự động thông báo và gửi dữ liệu

    Webhook là gì? Kiến thức cơ bản về Webhook

    Hướng dẫn dùng chmod trong PHP

    Trong hệ thống quản lý tập tin của server PHP, mỗi file và directory lại có một permission riêng

    Hướng dẫn dùng chmod trong PHP

    PHPMyadmin Lỗi: Maximum execution time of 300 seconds exceeded

    Lỗi này xuất hiện khi bạn thực hiện truy vấn trên PHPMyadmin, có thể do csdl của bạn quá

    PHPMyadmin Lỗi: Maximum execution time of 300 seconds exceeded

    Sửa lỗi khi upload file sử dụng Responsive Filemanager

    Tại một số phiên bản Responsive filemanager rất nhiều người sẽ gặp lỗi Error: SyntaxError:

    Sửa lỗi khi upload file sử dụng Responsive Filemanager

    Biểu thức chính quy trong PHP – Regular Expressions

    Biểu thức chính quy trong php đôi khi rất hữu dụng trong một số trường hợp kiểm tra và

    Biểu thức chính quy trong PHP – Regular Expressions

    Các lệnh SSH cơ bản để thao tác server

    Nhiều bạn đang hoạt động trong lĩnh vực web hoặc server và có thể đôi khi phải thao tác

    Các lệnh SSH cơ bản để thao tác server

    Cách khắc phục khi gửi mail vào Spam, Junk-Email

    Mail gửi đi bị vào Spam/Junk-Email của người nhận là do bộ lọc spam của Server nhận mail

    Cách khắc phục khi gửi mail vào Spam, Junk-Email

    Học Mysql

    Giới thiệu hệ quản trị CSDL MYSQL
    Giới thiệu hệ quản trị CSDL MYSQL
    MySQL hệ quản trị cơ sở dữ liệu mã nguồn mở thông dụng, đối với những ai thích ngon bổ rẻ thì đây là một hệ quản

    phần mềm quản lý xét nghiệm Covidi-19

    Đề thi tham khảo