使用CodeIgniter调用Xero API遇403认证失败问题求助
CodeIgniter调用Xero API出现403 Forbidden(AuthenticationUnsuccessful)问题排查
Array ( [Type] => [Title] => Forbidden [Status] => 403 [Detail] => AuthenticationUnsuccessful [Instance] => 114c05dd-3284-43bb-890f-223999170169 [Extensions] => Array ( ) )
我在使用CodeIgniter控制器调用Xero API获取发票数据时遇到上述403错误,请问这是代码问题,还是Xero应用未配置到位?以下是我的控制器代码:
<?php defined('BASEPATH') or exit('No direct script access allowed'); class Overdueinvoice extends MY_Controller { private $client_id; private $client_secret; private $redirect_uri; private $authorization_endpoint; private $token_endpoint; private $scopes; private $state; private $xero_tenant_id; public function __construct() { parent::__construct(); $this->load->helper('url'); // Initialize your OAuth 2.0 credentials here $this->client_id = 'YOUR_CLIENT_ID'; $this->client_secret = 'YOUR_CLIENT_SECRET'; $this->redirect_uri = 'YOUR_REDIRECT_URI'; $this->authorization_endpoint = 'https://login.xero.com/identity/connect/authorize'; $this->token_endpoint = 'https://identity.xero.com/connect/token'; $this->scopes = 'offline_access openid profile email accounting.transactions'; $this->state = '123'; $this->xero_tenant_id = 'YOUR_XERO_TENANT_ID'; } public function index() { header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE"); header("Access-Control-Allow-Headers: Content-Type, Authorization"); if (isset($_GET['code'])) { $this->handleXeroAuthorization(); } else { $this->redirectToXeroAuthorization(); } $data["title"] = "Overdue Invoices | Tremendio Portal"; $data["pagename"] = "Overdue Invoices"; $this->load_page2("overdueinvoice", $data, "overdueinvoice_footer.php", "overdueinvoice_header.php"); } private function handleXeroAuthorization() { // Xero OAuth 2.0 Credentials $client_id = $this->client_id; $client_secret = $this->client_secret; $redirect_uri = $this->redirect_uri; $authorization_endpoint = $this->authorization_endpoint; $token_endpoint = $this->token_endpoint; $scopes = $this->scopes; $state = $this->state; $xero_tenant_id = $this->xero_tenant_id; // Get the Xero tenant ID // Step 1: Handle the callback with the authorization code $authorization_code = $_GET['code']; // Step 2: Exchange the authorization code for an access token $token_request = array( 'grant_type' => 'authorization_code', 'code' => $authorization_code, 'redirect_uri' => $redirect_uri, ); $curl = curl_init($token_endpoint); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($token_request)); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Authorization: Basic ' . base64_encode($client_id . ':' . $client_secret), )); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $token_response = curl_exec($curl); curl_close($curl); // Step 3: Handle the access token and make API requests with it $token_data = json_decode($token_response, true); if (isset($token_data['access_token'])) { // Access token acquired, use it to make API requests, including the Xero tenant ID $access_token = $token_data['access_token']; // Store the access token and possibly the refresh token in your session $this->session->set_userdata('access_token', $access_token); $this->session->set_userdata('refresh_token', $token_data['refresh_token']); // Continue with your existing CodeIgniter code to fetch Xero data using the access token and tenant ID $api_url = 'https://api.xero.com/api.xro/2.0/Invoices?tenantId=' . $xero_tenant_id; $headers = array( 'Authorization: Bearer ' . $access_token, 'Content-Type: application/json', ); $ch = curl_init($api_url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); // Process and use the $response data as needed // You can return the data or do further processing here $data['xero_data'] = json_decode($response, true); // Assuming the response is JSON } else { // Handle the case where access token retrieval failed echo 'Access token retrieval failed'; } $this->load->view('overdueinvoice', $data); } private function redirectToXeroAuthorization() { // Step 4: Redirect the user to Xero's authorization page with scopes and state // Use class-level properties, not local variables $client_id = $this->client_id; $client_secret = $this->client_secret; $redirect_uri = $this->redirect_uri; $authorization_endpoint = $this->authorization_endpoint; $token_endpoint = $this->token_endpoint; $scopes = $this->scopes; $state = $this->state; // The rest of your code remains the same $authorize_url = $this->authorization_endpoint . '?client_id=' . $client_id . '&redirect_uri=' . $redirect_uri . '&response_type=code&scope=' . $scopes . '&state=' . $state; header('Location: ' . $authorize_url); exit; } }
问题排查及解决方案
一、Xero应用配置层面
- 重定向URI必须完全匹配:Xero开发者后台配置的
Redirect URI要和代码中$redirect_uri完全一致,包括协议(http/https)、域名、路径,不能有任何拼写或格式差异。 - 权限范围验证:确认Xero应用的权限设置中已启用
accounting.transactions,代码中声明的scopes必须和应用后台配置的权限一致。 - 租户ID有效性:不要硬编码租户ID,正确流程是在获取access_token后调用
GET /connections接口获取当前授权用户的有效租户ID,硬填的ID大概率不匹配。 - 应用状态检查:登录Xero开发者后台,确认应用处于激活状态,没有被限制或停用。
二、代码实现层面
- 修正租户ID传递方式:Xero API要求租户ID放在
Xero-tenant-id请求头中,而非URL参数。修改发票API请求的头信息:$api_url = 'https://api.xero.com/api.xro/2.0/Invoices'; $headers = array( 'Authorization: Bearer ' . $access_token, 'Content-Type: application/json', 'Xero-tenant-id: ' . $xero_tenant_id ); - 添加租户ID获取逻辑:在拿到access_token后,调用接口获取租户ID,替代硬编码:
// 获取租户ID $connections_url = 'https://api.xero.com/connections'; $conn_headers = array('Authorization: Bearer ' . $access_token); $conn_ch = curl_init($connections_url); curl_setopt($conn_ch, CURLOPT_HTTPHEADER, $conn_headers); curl_setopt($conn_ch, CURLOPT_RETURNTRANSFER, true); $conn_response = curl_exec($conn_ch); if(curl_errno($conn_ch)){ echo 'CURL Error: ' . curl_error($conn_ch); curl_close($conn_ch); return; } curl_close($conn_ch); $connections = json_decode($conn_response, true); if(!empty($connections)){ $xero_tenant_id = $connections[0]['tenantId']; $this->session->set_userdata('xero_tenant_id', $xero_tenant_id); } else { echo 'Failed to get tenant ID'; return; } - 添加CURL错误处理:在
curl_exec后检查请求错误,便于排查问题:if(curl_errno($ch)){ echo 'CURL Error: ' . curl_error($ch); } - 完善state参数验证:回调时验证返回的
state和发起授权时的一致,避免CSRF风险,也能排除参数篡改导致的认证失败。
三、认证流程层面
- 实现refresh_token刷新逻辑:access_token过期后,需要用refresh_token获取新的access_token,否则会持续出现认证失败。
内容的提问来源于stack exchange,提问作者Matt Mendoza
相关产品推荐
相关产品推荐

