Bài viết hướng dẫn lấy Zalo API access token bằng PHP. 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.
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 Platform chính thức chuyển sang sử dụng Zalo Login v4 để cấp access token. Để tìm hiểu phương pháp cũ, hãy xem bài viết Lấy access token bằng Zalo Login v3.
Việc lấy access token cho Official Account và User là tương tự nhau, chỉ khác URL tới Zalo Service.
Mục lục
Official Account Access Token
Luồng lấy oAuth access token cho Official Account như sau:
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.
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($code_verifier, $code_challenge); } ///// Authentication Process ///// if ( !isset($_SESSION["zalo_access_token"]) ) : $state = generate_state_param(); // for CSRF prevention // Generate the code verifier and code challenge list($code_verifier, $code_challenge) = 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"] = $code_verifier; $auth_uri = ZALO_PERMISSION_URL . "?" . http_build_query( array( "app_id" => APP_ID, "redirect_uri" => APP_AUTH_URL, "code_challenge" => $code_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_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 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, "/", $_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, "/", $_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
như phần cuối bài viết gọi Zalo API từ trình duyệt này.