PHP使用OAuth授权Azure应用访问Business Central API无返回问题
问题根因
你的代码存在多个逻辑和接口使用错误,所以拿不到授权码、返回NULL:
- 混淆了OAuth2授权码流程的两个核心步骤:授权码(code)是用户完成登录授权后Azure通过重定向回传的参数,
/authorize是供浏览器访问的交互端点,你用后端curl直接发POST请求根本无法触发用户授权交互,不可能拿到有效返回。 - 端点和参数错配:你往授权端点
/authorize发POST时携带了仅用于令牌端点的grant_type=authorization_code、client_secret参数,完全不符合Azure AD的接口规范。 - 权限范围配置错误:你要访问Business Central API,却配置了Microsoft Graph的scope,即使拿到令牌也没有对应API的访问权限。
- 缺失必填参数:所有OAuth2请求都必须携带和Azure应用注册后台配置完全一致的
redirect_uri(重定向地址),你的代码里完全没加这个参数。 - 缺少错误排查逻辑:代码没有捕获curl执行错误,当请求失败时
curl_exec会返回false,json_decode后自然得到NULL,无法定位具体错误。
修正方案
前置配置
先在Azure应用注册页面完成基础配置:
- 添加Web类型的重定向URI,地址填写你PHP应用的回调地址(例如
https://你的应用域名/callback.php),后续所有请求里的redirect_uri必须和这个值完全一致。 - 在API权限板块添加Business Central对应的委托权限,完成管理员授权(组织内部应用必须操作这一步)。
第一步:跳转用户到授权页拿授权码
这一步不需要用curl发POST,直接返回302跳转让用户浏览器访问Azure授权页即可,示例代码(login.php):
session_start(); $tenant = "你的Azure租户ID/域名"; $clientId = "你的应用客户端ID"; $redirectUri = urlencode("https://你的应用域名/callback.php"); $scope = urlencode("https://api.businesscentral.dynamics.com/.default offline_access"); // 生成随机state做CSRF校验,存入session $state = bin2hex(random_bytes(5)); $_SESSION['oauth_state'] = $state; $authUrl = "https://login.microsoftonline.com/{$tenant}/oauth2/v2.0/authorize?client_id={$clientId}&response_type=code&redirect_uri={$redirectUri}&response_mode=query&scope={$scope}&state={$state}"; header("Location: {$authUrl}"); exit;
用户完成登录授权后,Azure会自动重定向到你配置的callback.php,URL的query参数中会携带code,也就是你需要的授权码。
第二步:在回调页用授权码换访问令牌
在callback.php中先做安全校验,再用curl请求令牌端点换取可调用API的access_token,示例代码:
session_start(); // 校验state防CSRF if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['oauth_state']) { die("非法请求:state校验不通过"); } // 捕获授权阶段返回的错误 if (isset($_GET['error'])) { die("授权失败:" . $_GET['error_description']); } $authCode = $_GET['code']; // 这里就是获取到的授权码 $tenant = "你的Azure租户ID/域名"; $clientId = "你的应用客户端ID"; $clientSecret = "你的应用客户端密钥"; $redirectUri = "https://你的应用域名/callback.php"; $tokenEndpoint = "https://login.microsoftonline.com/{$tenant}/oauth2/v2.0/token"; $ch = curl_init($tokenEndpoint); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"], CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'authorization_code', 'client_id' => $clientId, 'client_secret' => $clientSecret, 'code' => $authCode, 'redirect_uri' => $redirectUri, 'scope' => 'https://api.businesscentral.dynamics.com/.default offline_access' ]) ]); $response = curl_exec($ch); // 捕获curl层错误 if (curl_errno($ch)) { die("请求失败:" . curl_error($ch)); } curl_close($ch); $tokenRes = json_decode($response, true); // 捕获令牌接口返回的业务错误 if (isset($tokenRes['error'])) { die("换取令牌失败:" . $tokenRes['error_description']); } // 从返回结果中取access_token,即可携带该令牌调用Business Central API $accessToken = $tokenRes['access_token'];
注意事项
- 废弃老的
login.windows.net端点,统一使用login.microsoftonline.com的v2.0版本OAuth端点,兼容性和稳定性更好。 - 如果是无用户交互的后台服务场景,不需要走授权码流程,直接使用客户端凭据流(
grant_type=client_credentials)请求令牌端点即可拿到应用权限的访问令牌。 - 所有URL传参必须做urlencode处理,避免特殊字符导致请求解析失败。
- 所有HTTP请求必须增加错误捕获逻辑,禁止直接对返回值做json_decode,否则请求出错时只会得到NULL,无法排查问题。
内容的提问来源于stack exchange,提问作者Matthijs
相关产品推荐
相关产品推荐

