当前位置: 首页 > 知识库问答 >
问题:

Azure Devops Oauth身份验证:无法获取访问令牌(BadRequest未能反序列化JsonWebToken对象)

胡越
2023-03-14

我试图为定制webapplication for Azure DevOps实现一个OAuth2.0流。我正在遵循https://docs.microsoft.com/en-us/Azure/DevOps/integrat/get-start/authentication/oAuth?view=azure-devops文档,以及https://github.com/microsoft/azure-devops-auth-samples/tree/master/OauthWebSample OauthWebSample,但使用ASP.NET核心(我还阅读了一个问题,所以看起来类似,但不是:Access Azure DevOps REST API with oAuth)

我已经在https://app.vsaex.visualstudio.com/app/register注册了一个azdo应用程序,授权步骤似乎运行良好,即用户可以授权该应用程序,重定向到我的应用程序将返回一些类似于有效jwt令牌的内容:

header: {
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "oOvcz5M_7p-HjIKlFXz93u_V0Zo"
}
payload: {
  "aui": "b3426a71-1c05-497c-ab76-259161dbcb9e",
  "nameid": "7e8ce1ba-1e70-4c21-9b51-35f91deb6d14",
  "scp": "vso.identity vso.work_write vso.authorization_grant",
  "iss": "app.vstoken.visualstudio.com",
  "aud": "app.vstoken.visualstudio.com",
  "nbf": 1587294992,
  "exp": 1587295892
}

下一步是获取一个访问令牌,该令牌以badreqest失败:invalid_client,未能反序列化JsonWebToken对象。

以下是完整的示例:

public class Config
{
    public string ClientId { get; set; } = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
    public string Secret { get; set; } = "....";
    public string Scope { get; set; } = "vso.identity vso.work_write";
    public string RedirectUri { get; set; } = "https://....ngrok.io/azdoaccount/callback";
}

/// <summary>
/// Create azdo application at https://app.vsaex.visualstudio.com/
/// Use configured values in above 'Config' (using ngrok to have a public url that proxies to localhost)
/// navigating to localhost:5001/azdoaccount/signin 
/// => redirect to https://app.vssps.visualstudio.com/oauth2/authorize and let user authorize (seems to work)
/// => redirect back to localhost:5001/azdoaccount/callback with auth code
/// => post to https://app.vssps.visualstudio.com/oauth2/token => BadReqest: invalid_client, Failed to deserialize the JsonWebToken object
/// </summary>
[Route("[controller]/[action]")]
public class AzdoAccountController : Controller
{
    private readonly Config config = new Config();
    [HttpGet]
    public ActionResult SignIn()
    {
        Guid state = Guid.NewGuid();

        UriBuilder uriBuilder = new UriBuilder("https://app.vssps.visualstudio.com/oauth2/authorize");
        NameValueCollection queryParams = HttpUtility.ParseQueryString(uriBuilder.Query ?? string.Empty);

        queryParams["client_id"] = config.ClientId;
        queryParams["response_type"] = "Assertion";
        queryParams["state"] = state.ToString();
        queryParams["scope"] = config.Scope;
        queryParams["redirect_uri"] = config.RedirectUri;

        uriBuilder.Query = queryParams.ToString();

        return Redirect(uriBuilder.ToString());
    }

    [HttpGet]
    public async Task<ActionResult> Callback(string code, Guid state)
    {
        string token = await GetAccessToken(code, state);
        return Ok();
    }

    public async Task<string> GetAccessToken(string code, Guid state)
    {
        Dictionary<string, string> form = new Dictionary<string, string>()
                {
                    { "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
                    { "client_assertion", config.Secret },
                    { "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" },
                    { "assertion", code },
                    { "redirect_uri", config.RedirectUri }
                };

        HttpClient httpClient = new HttpClient();

        HttpResponseMessage responseMessage = await httpClient.PostAsync(
            "https://app.vssps.visualstudio.com/oauth2/token",
            new FormUrlEncodedContent(form)
        );
        if (responseMessage.IsSuccessStatusCode) // is always false for me
        {
            string body = await responseMessage.Content.ReadAsStringAsync();
            // TODO parse body and return access token
            return "";
        }
        else
        {
            // Bad Request ({"Error":"invalid_client","ErrorDescription":"Failed to deserialize the JsonWebToken object."})
            string content = await responseMessage.Content.ReadAsStringAsync();
            throw new Exception($"{responseMessage.ReasonPhrase} {(string.IsNullOrEmpty(content) ? "" : $"({content})")}");
        }
    }
}

共有1个答案

欧阳炜
2023-03-14

请求访问令牌时,必须为client_assertion参数提供客户端秘密而不是应用程序秘密:

 类似资料:
  • 我询问了如何建立一个服务呼叫,并在HttpClient上获得了一个很好的信息。然而,虽然这个问题在技术上得到了回答,但我还是被卡住了。 在控制台中,我可以看到我的浏览器向服务发送了什么请求来获取授权令牌。然而,当我尝试在我的服务层中模拟构建请求的调用时,我得到以下错误消息。我在这里犯错的可能性很大。不知道该用谷歌搜索什么,真的。。。 "StatusCode: 500, ReasonPhrase:'

  • 我有一个Rest Spring BootAPI,当用户验证API返回令牌jwt时,我在浏览器中注意到该令牌出现在响应头中 如何通过Reactjs将此令牌存储在本地存储浏览器中? 我的请求代码如下所示:

  • 我有一个LaravelAPI(实际上是LumenAPI)服务于VueJS前端。Vue应用程序允许用户登录到谷歌。然后将Google令牌发送回Lumen API,后者使用Google验证令牌,然后验证电子邮件地址是否为有效用户。然后它生成一个令牌,与用户一起存储在数据库中,并返回用户对象。 我没有使用Passport或jwt auth之类的东西。那么现在,我如何使用默认的Auth中间件来验证(现在已

  • 我试图弄清楚我应该如何坚持身份验证。 假设用户使用电子邮件和密码成功进行身份验证。然后服务器生成并返回两个令牌: accesstoken(jwt过期15分钟)将存储在浏览器存储中 refreshtoken(jwt过期7天)作为安全cookie 当将访问令牌存储在本地存储(或会话存储)中时,React 应用程序将简单地检查它是否存在于存储中并继续渲染私有路由。因此,这意味着如果用户有一个无效/被盗的

  • 当且仅当用户在发出OAuth2请求时登录到LinkedIn时,它才起作用。 如果用户未登录,则会遇到错误。 我们的行动顺序: 成功获取新的访问令牌 使用访问令牌,发布到apiendpoint 之后,我们将收到一份401,内容如下: 有时,经过一段时间后,使用相同的访问令牌重试会得到200。有时不会。 如果用户在“401期间”登录LinkedIn,那么之前获取的访问令牌就会神奇地开始工作。 我不知道

  • 我试图使用从Spring应用程序中的公共客户端获取访问令牌。 谁能帮我弄清楚我做错了什么吗?