C#实现GitHub App认证遇403错误,iat/exp格式不符求助
问题
开发基于GitHub App Authentication的认证功能用于同步两个GitHub仓库,参考官方文档实现后,C#代码生成JWT调用GitHub API返回403 Forbidden错误。用Postman测试同一JWT时,提示:
'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued
查看JWT Payload发现iat和exp是long类型,不符合要求的整数格式。最终目标是调用如https://api.github.com/repos/OWNER/REPO/contents/PATH这类GitHub API。
当前代码如下:
String PEMString = "xxxxx"; JsonWebTokenHandler JWTHandler = new JsonWebTokenHandler(); DateTime Now = DateTime.UtcNow; PemReader Reader = new PemReader(new MemoryStream(Encoding.UTF8.GetBytes(PEMString))); RsaSecurityKey RSAKey = new RsaSecurityKey(Reader.ReadRsaKey()); SigningCredentials Credentials = new SigningCredentials(RSAKey, SecurityAlgorithms.RsaSsaPssSha256); JObject Payload = new JObject(); Payload.Add("iat", Now.TimeOfDay.Ticks); Payload.Add("exp", Now.AddHours(1).TimeOfDay.Ticks); Payload.Add("iss", <my app id>); String JWTToken = JWTHandler.CreateToken(Payload.ToString(), Credentials); HttpClient Client = new HttpClient(); Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", JWTToken); Client.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json"); Client.GetAsync(new Uri("https://api.github.com/app/installations"));
解决方案
错误根源
- 时间值完全错误:GitHub要求JWT的
iat和exp是Unix时间戳(秒级)——也就是从1970-01-01 UTC到当前时间的秒数,而你代码里用的TimeOfDay.Ticks是当天的纳秒级数值,完全不符合要求。 - 序列化类型问题:就算用了正确的时间戳,直接用
JObject添加long类型值,序列化后可能会被识别为long而非整数格式,需要明确指定类型让序列化工具输出整数。
修正步骤
- 计算正确的Unix秒级时间戳;
- 用强类型的Claims集合构建Payload,让
JsonWebTokenHandler自动处理整数序列化。
修正后的代码
string pemString = "xxxxx"; var jwtHandler = new JsonWebTokenHandler(); DateTime now = DateTime.UtcNow; // 读取RSA密钥 using var reader = new PemReader(new MemoryStream(Encoding.UTF8.GetBytes(pemString))); var rsaKey = new RsaSecurityKey(reader.ReadRsaKey()); var signingCredentials = new SigningCredentials(rsaKey, SecurityAlgorithms.RsaSsaPssSha256); // 构建Claims集合,明确指定值类型为整数 var claims = new List<Claim> { new Claim("iat", new DateTimeOffset(now).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim("exp", new DateTimeOffset(now.AddHours(1)).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim("iss", "<your-app-id>") // 替换成你实际的GitHub App ID }; // 创建JWT令牌 var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), SigningCredentials = signingCredentials }; var jwtToken = jwtHandler.CreateToken(tokenDescriptor); // 调用GitHub API using var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json"); var response = await client.GetAsync(new Uri("https://api.github.com/app/installations")); response.EnsureSuccessStatusCode(); // 主动检查响应状态
额外注意点
iss字段必须填你在GitHub上创建的App的ID,是个整数;- GitHub限制JWT有效期不能超过1小时,代码里设置的1小时符合要求;
- 要调用仓库相关接口(比如
/repos/OWNER/REPO/contents/PATH),还得先用这个JWT获取Installation Access Token,再用这个Token去调用具体接口。
内容的提问来源于stack exchange,提问作者delucaezequiel
相关产品推荐
相关产品推荐

