Charleh
Charleh

Reputation: 14012

OAuth authorization_code flow unauthorized unless done via Postman

I'm trying to get an application to work with the Microsoft Identity platform.

Sending an OAuth request via Postman appears to work, but when I try the authorizaton_code grant type myself, despite getting back an access token, the API that I'm trying to access always gives me an unauthorized error.

I'm sending a POST request to:

https://login.windows.net/<tenant_id>/oauth2/authorize?resource=<resource_uri>

With body data:

grant_type=authorization_code&
client_id=<client_id>&
redirect_uri=<redirect_uri>&
response_type=code

This gives me a redirect to my URI with the code in the querystring

I then request an access token with the code by sending a POST to:

https://login.windows.net/<tenant_id>/oauth2/token?resource=<resource_uri>

With the following content:

grant_type=authorization_code&
client_id=<client_id>&
redirect_uri=<redirect_uri>&
code=<the_code_from_the_redirect>&
client_secret=<client_secret>

This gives me back an access token:

{
    "token_type": "Bearer",
    "expires_in": "3599",
    "ext_expires_in": "3599",
    "expires_on": "1557783183",
    "not_before": "1557779283",
    "resource": "00000002-0000-0000-c000-000000000000",
    "access_token": "<access_token_here>",
    "refresh_token": "<refresh_token>",
    "id_token": "<id_token>"
}

But this token doesn't work when calling the resource:

{
    "error": {
        "code": "Unauthorized",
        "message": "The credentials provided are incorrect"
    }
}

Doing the same using the Get New Access Token functionality in Postman seems to create the same post request in the Postman console (albeit with a different code, but it has to get a new code because I've already redeemed the other one and it knows nothing about it right?) but the access token that it returns works:

{
    "error": {
        "code": "NoLicense",
        "message": "User has no license"
    }
}

(Ignore the fact that it's an error - the user has no licence for the application I'm trying to query but that's ok)

Am I doing something fundamentally wrong? From what I can see, I'm following the OAuth flow correctly.

Upvotes: 0

Views: 920

Answers (1)

Charleh
Charleh

Reputation: 14012

Figured this out - it's because I was passing the resource as part of the querystring and not part of the form.

When the identity platform generates the redirect/callback it only appears to include either the querystring elements when doing a GET or the form elements when doing a POST.

You can see that in the below:

<html>
    <head>
        <title>Continue</title>
    </head>
    <body>
        <form method="POST" name="hiddenform" action="https://login.microsoftonline.com/<tenant_id>/oauth2/authorize">
            <input type="hidden" name="grant_type" value="authorization_code" />
            <input type="hidden" name="client_id" value="<client_id>" />
            <input type="hidden" name="redirect_uri" value="https://businesscentral.dynamics.com" />
            <input type="hidden" name="response_type" value="code" />
            <input type="hidden" name="scope" value="" />
            <noscript>
                <p>Script is disabled. Click Submit to continue</p>
                <input type="submit" value="Submit" />
            </noscript>
        </form>
        <script language="javascript">window.setTimeout('document.forms[0].submit()', 0);</script>
    </body>
</html>

After adding resource to the form data instead of the URL I got a slightly different redirect:

<html>
    <head>
        <title>Continue</title>
    </head>
    <body>
        <form method="POST" name="hiddenform" action="https://login.microsoftonline.com/<tenant_id>/oauth2/authorize">
            <input type="hidden" name="grant_type" value="authorization_code" />
            <input type="hidden" name="client_id" value="<client_id>" />
            <input type="hidden" name="redirect_uri" value="https://businesscentral.dynamics.com" />
            <input type="hidden" name="response_type" value="code" />
            <input type="hidden" name="scope" value="" />
            <input type="hidden" name="resource" value="<resource_id>" />
            <noscript>
                <p>Script is disabled. Click Submit to continue</p>
                <input type="submit" value="Submit" />
            </noscript>
        </form>
        <script language="javascript">window.setTimeout('document.forms[0].submit()', 0);</script>
    </body>
</html>

Which generated me the correct access token!

Upvotes: 1

Related Questions