0

In a service-to-service oauth communication, I'm trying to send a token request to an external Oauth Token Endpoint and to use the token as a Bearer for a Microsoft solution (Dynamics CRM v.9.1 On-Premises). The platform used should not be so relevant since we are talking about OAuth which is an RFC documented protocol The code I used so far to get the token and to make the request to the Resource Server with the Bearer is the one pasted below.

When I make the request to the Resource server i get the following error.

Bearer error=invalid_token, error_description=Error during token validation!, 
authorization_uri=https://mytokenserver/login, 
resource_id=https://myresourceserver/

The problem in the response is that it shows an "authorization_uri" which is https://mytokenserver/login , which is wrong. My authorization uri is : https://mytokenserver/oauth2/authorize

I wasn't able to find any setting in the microsoft crm platform so my best guess is that I'm generating a wrong Assertion JWT in the code below.

Any experience with that ? (just a small detail, the oauth token server is written in java)

    static private string clientId     = "rb7ddjkjWd8djkjlk";
    static private string pfxFile      = "C:\\keystore.p12";
    static private string pass         = "blabla";
    static private string authorityUri = "https://mytokenserver/oauth2/token";

    static private Uri environmentUri  = new Uri("https://myresourceserver/api/data/v9.1");

    static async Task<string> RequestTokenAndSendRequestToResourceServerAsync()
    {
        if (tokenCache != "")
            return tokenCache;

        var client = new HttpClient();

        var clientToken = GetJwtToken(pfxFile, pass, clientId, authorityUri, 180);

        Console.WriteLine("JWT Token is: " + clientToken);

        Parameters parameters = new Parameters
        {
            new KeyValuePair<string, string>("audience", clientId),
            new KeyValuePair<string, string>("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
        };
        ClientCredentialsTokenRequest clientCredentialsRequest = new ClientCredentialsTokenRequest
        {
            Parameters = parameters,
            GrantType = OidcConstants.GrantTypes.ClientCredentials,
            Address = authorityUri,
            ClientAssertion =
            {
                Type = OidcConstants.ClientAssertionTypes.JwtBearer,
                Value = clientToken
            }
        };
        clientCredentialsRequest.ClientCredentialStyle = ClientCredentialStyle.PostBody;
        var response = await client.RequestClientCredentialsTokenAsync(clientCredentialsRequest);

        if (response.IsError)
        { 
            Console.WriteLine(response.HttpStatusCode);
            Console.WriteLine(response.ErrorDescription);
            Console.WriteLine(response.ErrorType.ToString());
            throw response.Exception;
        }
        Console.WriteLine("Access Token is: " + response.AccessToken);

        // Set up the HTTP client
        var httpclient = new HttpClient
        {
            BaseAddress = new Uri("https://myresourceserver"),
            Timeout = new TimeSpan(0, 2, 0)  // Standard two minute timeout.
        };

        HttpRequestHeaders headers = httpclient.DefaultRequestHeaders;
        headers.Authorization = new AuthenticationHeaderValue("Bearer", response.AccessToken);
        headers.Add("OData-MaxVersion", "4.0");
        headers.Add("OData-Version", "4.0");
        headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        // Web API call
        var result = httpclient.GetAsync("WhoAmI").Result;

        var jsonResponse = await result.Content.ReadAsStringAsync();

        Console.WriteLine(result.ReasonPhrase);
        Console.WriteLine(jsonResponse);
        result.EnsureSuccessStatusCode();

        return response.AccessToken;
    }
    

    public static string GetJwtToken(string pfxFilePath, string password, string issuer, string audience, int expiryInMinutes)
    {
        Console.WriteLine("Creating JWT Token");
        string jwtToken = string.Empty;
        JwtSecurityToken jwtSecurityToken;

        X509Certificate2 signingCert = new X509Certificate2(pfxFilePath, password);
        X509SecurityKey privateKey = new X509SecurityKey(signingCert);

        var descriptor = new SecurityTokenDescriptor
        {
            // Audience = auth2/token endpoint, Issuer = clientId                
            Issuer = issuer,
            Audience = audience,
            IssuedAt = DateTime.UtcNow,
            Expires  = DateTime.UtcNow.AddMinutes(expiryInMinutes),
            Subject  = new ClaimsIdentity(new List<Claim>
            {
                new Claim(JwtClaimTypes.Subject, issuer),
                new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
            }),
            SigningCredentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256)
        };

        var handler = new JwtSecurityTokenHandler();
        handler.SetDefaultTimesOnTokenCreation = false;
        jwtSecurityToken = handler.CreateJwtSecurityToken(descriptor);
        jwtSecurityToken.Header.Remove("kid");

        jwtToken = handler.WriteToken(jwtSecurityToken);
        return jwtToken;
    }
Claudio Ferraro
  • 4,551
  • 6
  • 43
  • 78
  • See https://learn.microsoft.com/en-us/connectors/custom-connectors/troubleshoot-oauth2 Using Powershell can help troubleshoot OAUTH issues : https://www.mistercloudtech.com/2022/02/28/how-to-fix-create-powershell-session-is-failed-using-oauth-exo-v2-powershell-error/ and https://learn.microsoft.com/en-us/answers/questions/204156/connect-exchangeonline-(oath-fun) – jdweng Jul 29 '23 at 23:29
  • Thank You for the links. I followed all the information anyway something seems to be wrong in the behavior of the resource server. Where does the resource server take the information that the token must be verified on the login endpoint and not on the authorize endpoint. – Claudio Ferraro Jul 29 '23 at 23:44
  • There is more than one type of authentication. See https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-7.0 – jdweng Jul 30 '23 at 07:49

1 Answers1

0

My guess is that the resource server you're calling does not allow client credential access tokens. It probably requires a user to be a subject of the token, not your service. This could explain the error message. The server expects that the endpoint for the authorization code request is the one present in the token. You have the token endpoint (which is used in the client credentials flow).

Michal Trojanowski
  • 10,641
  • 2
  • 22
  • 41
  • what do You mean with the one present in the token ? – Claudio Ferraro Aug 02 '23 at 12:57
  • I understood that the `authorization_uri` ends up as a claim in the access token, but maybe I'm mistaken. Anyway, I read the error message that the server expected a token initiated with the authorization code flow but you sent a token issued with the client credentials flow. – Michal Trojanowski Aug 07 '23 at 08:54