-2

I have a postman request that works correctly in obtaining a bearer token, that I can grab and use to make a successful request. But I cannot obtain an equally valid bearer token in c# and I have tried about 100 different approaches.

Here is the Postman request, which gets a valid bearer token:

enter image description here

Scope: AX.FullAccess CustomService.FullAccess Odata.FullAccess

Auth URL: https://login.microsoftonline.com/abe3ad26.../oauth2/authorize?resource=https://myCust.sandbox.operations.dynamics.com/

In the Client Authentication pulldown there are 2 options. The other option is Send as basic auth header. It doesn't matter which I choose, they both work.

And here is my non-working c# attempting to do the same thing postman does:

using(WebClient client = new WebClient()) {
  var querystring = new System.Collections.Specialized.NameValueCollection();
  querystring.Add("grant_type", "authorization_code");
  querystring.Add("client_id", "e93c4014...");
  querystring.Add("client_secret", "USu8Q...");
  querystring.Add("redirect_uri", "https://myCust.sandbox.operations.dynamics.com/");
  querystring.Add("resource", "https://myCust.sandbox.operations.dynamics.com/");
  querystring.Add("scope", "AX.FullAccess CustomService.FullAccess Odata.FullAccess");

  byte[] responsebytes = client.UploadValues("https://login.microsoftonline.com/abe3ad26.../oauth2/authorize", "POST", querystring);

  [code to retrieve the response...]
}

In all the various permutations I have tried of the above code, I either get an exception with no message, or I get a large html response that is merely Microsoft's generic page titled Sign in to your account.

I'm unsure whether resource should be a formal param, or appended to the Auth URL as in postman, or what.

I am certain the client_id and client_secret are right, both in name and value. As for all the other params, I'm not as sure. I'm especially unsure what exact url I should be POSTing this request to. I have tried the full Auth URL with and without the resource querystring value appended, no luck either way.

halfer
  • 19,824
  • 17
  • 99
  • 186
HerrimanCoder
  • 6,835
  • 24
  • 78
  • 158
  • 1
    Herriman, a quick note. You seem to be using Stack Overflow as one would a chatroom, and the many edits you have received don't seem to have changed your posting style. Technical writing is still a requirement here ([canonical reference](https://meta.stackoverflow.com/questions/288160/no-thanks-damn-it)). Editors do try to exercise patience with new members, but you have been here ten years, and do not appear to be struggling with English. Are you not seeing edit notifications? I can report a bug on Meta Stack Overflow if you are not seeing post improvements. – halfer May 22 '22 at 12:33
  • 1
    I often post this boilerplate advice: _Note that we prefer a technical style of writing here. We gently discourage greetings, hope-you-can-helps, thanks, advance thanks, notes of appreciation, regards, kind regards, signatures, please-can-you-helps, chatty material and abbreviated txtspk, pleading, how long you've been stuck, voting advice, meta commentary, etc. Just explain your problem, and show what you've tried, what you expected, and what actually happened_. – halfer May 22 '22 at 12:33

1 Answers1

-2

One issue is that you should always ask for the openid scope.

this is a required scope in most identity providers.

Below is the C# code I use to get the token from an openid-connect token provider: (using the Flurl.Http NuGet package)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Microsoft.AspNetCore.Mvc;
using OpenID_Connect_client.Models;

public class CodeFlowController : Controller
{
    private readonly IOpenIDSettings _openIdSettings;

    public CodeFlowController(IOpenIDSettings openIdSettings)
    {
        this._openIdSettings = openIdSettings;
    }

    /// <summary>
    /// Start page for the login using the fragment as return type
    /// </summary>
    /// <returns></returns>
    public IActionResult Login()
    {
        string url = BuildLoginUrl();
        ViewData["loginurl"] = url;

        return View();
    }


    /// <summary>
    /// Construct the login URL
    /// </summary>
    /// <returns></returns>
    private string BuildLoginUrl()
    {
        //In real life, nonce and state should be random values, but we hardcoded them here for simplicity
        string nonceValue = "1122334455";
        string stateValue = "9988776655";

        //Please redirect us back to this page after successful login
        string redirectUrl = "https://localhost:5001/CodeFlow/callback";

        var url = new Url(_openIdSettings.authorization_endpoint);

        url = url.SetQueryParams(new
        {
            response_type = "code",       //Get both access-token + ID-token
            client_id = "authcodeflowclient",       //Id of this client

            scope = "openid email profile shop.admin",   //openid (required; to indicate that the application intends to use OIDC to verify the user's identity)

            prompt = "consent",                     // Force users to provide consent
            response_mode = "form_post",            // Send the token response as a form post instead of a fragment encoded redirect

            state = stateValue,                     // To prevent CSRF attacks
            nonce = nonceValue,                     // To further strengthen the security

            redirect_uri = redirectUrl              // The URL to which the Auth0 will redirect the user's browser after authorization has been granted by the user. 
        });

        return url.ToString();
    }


    /// <summary>
    /// This method is called with the authorization code and state parameter
    /// </summary>
    /// <param name="code">authorization code generated by the authorization server. This code is relatively short-lived, typically lasting between 1 to 10 minutes depending on the OAuth service.</param>
    /// <param name="state"></param>
    /// <returns></returns>
    [HttpPost]
    public IActionResult Callback(string code, string state)
    {

        //To be secure then the state parameter should be compared to the state sent in the previous step

        var url = new Url(_openIdSettings.token_endpoint);

        var token = url.PostUrlEncodedAsync(new
        {
            client_id = "authcodeflowclient",       //Id of this client
            client_secret = "mysecret",
            grant_type = "authorization_code",
            code = code,
            redirect_uri = "https://localhost:5001/CodeFlow/Callback"

        }).ReceiveJson<Token>().Result;

        return View(token);
    }
}
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • The postman example doesn't ask for openId scope and it works. – HerrimanCoder May 16 '22 at 12:03
  • I would compare the requests using a tool like getfiddler.com to compare the request from PostMan and the one from c# – Tore Nestenius May 16 '22 at 12:07
  • I already did that using postman console - it's very detailed, like fiddler. But there is lots of noise and chatter in there and I'm having trouble filtering out and seeing what actually needs to change in my c# code to achieve the same result. – HerrimanCoder May 16 '22 at 12:19
  • Better to use Fiddler, because then you can compare both imbplementations in the same tool. But beware that getting a token using Oauth with the auth code flow is a multi-step process, its not a single request... – Tore Nestenius May 16 '22 at 12:29
  • Yes, that multistep process (as shown in postman console) is giving me trouble, getting c# to do the same thing. Thanks for your time and attention, I'll try fiddler. When I try to do exactly in my c# what postman console shows, I get different results. – HerrimanCoder May 16 '22 at 12:31
  • Yes, implementing it yourself is an interesting exercise, but in a real case I would use the built in OpenIDConnect handler to authenticate the users and retrieving the tokens. My code in the answer does that. Then there are a bunch of security layers that can cause trouble like PKCE – Tore Nestenius May 16 '22 at 12:33