微软设备码登录
设备码登录(OAuth 2.0 device code)是正版账户的唯一入口。用户点「登录 Microsoft」,启动器显示一串 user_code 和一个验证地址,用户去浏览器里输码授权,启动器在后台轮询换取令牌。本文讲这条链路的每一步、真实请求,以及两个会让人卡住的坑。
申请设备码
POST https://login.live.com/oauth20_connect.srf
Content-Type: application/x-www-form-urlencoded
client_id=00000000402b5328
&scope=service::user.auth.xboxlive.com::MBI_SSL
&response_type=device_codeclient_id 是 Minecraft 官方启动器的公开 ID,scope 是 Xbox Live 的 MBI_SSL 服务。响应里取四个字段:user_code(给用户看的码)、device_code(轮询时用)、verification_uri(用户该打开的地址)、interval(建议的轮询间隔秒数)。
轮询换票
POST https://login.live.com/oauth20_token.srf
client_id=00000000402b5328
&device_code=<device_code>
&grant_type=device_code未授权时返回的不是 4xx 空体
legacy 端点在用户还没授权时返回非 2xx,但响应体仍是合法 JSON,里面是 error 字段。所以必须先读 body 再判状态码,不能只看 IsSuccessStatusCode——否则你只会拿到一句「获取设备码失败」,看不到真正原因。
轮询结果按 error 分类:
authorization_pending→ 用户还没点确认,继续等slow_down→ 轮询太快,把间隔调大后继续- 其它 error → 终态,翻译成可读提示(
authorization_declined、expired_token、invalid_grant等)
拿到 access_token 和 refresh_token 之后,才进入下面的认证链。
设备码是一次性的
设备码在令牌被兑现的那一刻就作废了。所以认证链失败必须作为终态返回,不能让调用方拿同一个 device_code 再试——第二次只会得到「授权已失效」,把真实原因(比如 XErr 2148916233)盖掉。
XBL → XSTS → Minecraft
这三步是 Minecraft 认证链的固定形状,代码在 MicrosoftAuthService.AuthenticateWithMicrosoftTokenAsync 里。
1. Xbox Live,POST https://user.auth.xboxlive.com/user/authenticate:
{
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": "t=<msAccessToken>"
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
}注意 RpsTicket 的 t= 前缀。legacy live.com 令牌用 t=,d= 只适用于自注册 Azure 应用的 v2 令牌。响应里拿 Token 和 DisplayClaims.xui[0].uhs——uhs 就是这个用户的 xuid,启动游戏时作 --xuid 传进去。
2. XSTS,POST https://xsts.auth.xboxlive.com/xsts/authorize:
{
"Properties": { "SandboxId": "RETAIL", "UserTokens": ["<xblToken>"] },
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT"
}失败时响应体是 XErr 而不是 error,值得单独翻译:
| XErr | 含义 |
|---|---|
| 2148916233 | 该微软账户还没建 Xbox Live 账号 |
| 2148916235 | 所在地区不支持 Xbox Live |
| 2148916238 | 未成年人账户,需要家长同意 |
3. 换 Minecraft 令牌,POST https://api.minecraftservices.com/authentication/login_with_xbox:
{ "identityToken": "XBL3.0 x=<uhs>;<xstsToken>" }拿到 access_token,这才是后面所有 Minecraft API 的凭证。
读档案
GET https://api.minecraftservices.com/minecraft/profile
Authorization: Bearer <minecraftAccessToken>id 是无连字符的 32 位 UUID(游戏内用的短横线形式要自己插),name 是角色名。404 表示这个微软账户没有 Java 版。
令牌刚下发时可能还没传播
login_with_xbox 刚返回的 token,Minecraft 服务端有时还没同步完,这时候查档案会拿到 401。这不是账号有问题,是服务端最终一致。我们的做法是退避重试:
if (profile.StatusCode == System.Net.HttpStatusCode.Unauthorized && attempt < 3)
{
LauncherLog.Write("auth", $"档案查询第 {attempt} 次 401(令牌尚未传播),1 秒后重试");
await Task.Delay(TimeSpan.FromSeconds(1), ct);
continue;
}三次都 401 才当失败。
为什么走 legacy 端点而不是 v2
这是最容易踩的一个坑。Minecraft 官方的 client_id=00000000402b5328 不属于任何 Entra 租户,打到 login.microsoftonline.com/common/oauth2/v2.0/devicecode 会直接被拒:
AADSTS700016: Application with identifier '00000000402b5328' was not found in the directory.所以申请和换票都必须走 legacy 的 login.live.com/oauth20_connect.srf / oauth20_token.srf。这个端点是微软为老式 MSA 客户端保留的,接受 response_type=device_code 和 service::user.auth.xboxlive.com::MBI_SSL 这种 scope 写法。
别自作聪明升级端点
看到 v2 端点「更现代」就换过去,是这段代码最常见的改坏方式。判断标准只有一条:这个 client_id 属不属于某个租户。不属于,就老老实实待在 legacy。
续期
用 refresh_token 换新会话时,仍然打 oauth20_token.srf,参数换成 grant_type=refresh_token 并带上 scope。微软可能返回新的 refresh_token,也可能不返回——不返回就沿用旧的(rotated ?? refreshToken)。落地策略见令牌存储策略。
相关链接:第三方认证 · 离线账户与 UUID 算法 · 令牌存储策略 · 皮肤库与换肤