AzureAD / MSAL.PS

MIT License
159 stars 29 forks source link

Using ExtraQueryParameters for Authorization code with PKCE Flow #47

Closed darrenjrobinson closed 2 years ago

darrenjrobinson commented 2 years ago

I'm trying to use MSAL.PS for an Authorization code with PKCE Flow. PKCE was introduced in MSAL.NET with MSAL 4.30.0 .

Intended Process: Initiate an interactive AuthN with PKCE parameters to get an Authorisation Code to then use to get an Access Token.

Error:

Get-MsalToken : Duplicate query parameter 'code_challenge' in extraQueryParameters. 
At line:1 char:13
+ $authCode = Get-MsalToken -ClientId $clientID `
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : AuthenticationError: (Microsoft.Ident...arameterBuilder:AcquireTokenInteractiveParameterBuilder) [Write-Error], MsalClientException
    + FullyQualifiedErrorId : GetMsalTokenFailureAuthenticationError,Get-MsalToken

Example to repo:

$authCode = Get-MsalToken -ClientId $clientID `
    -TenantId $tenantID `
    -Interactive `
    -RedirectUri $replyURL `
    -Scopes "https://graph.microsoft.com/.default" `
    -ExtraQueryParameters @{
        response_type = 'code'
        response_mode = 'query'
        state = $codeChallenge.Substring(0, 27)
        code_challenge_method = 'S256'
        code_challenge = 'M3ajh9Hx7lZqQ......4jOStyDAUyRruicBxE'
    } `
    -Verbose

Alternate: Introduce another option with MSAL.PS to support the Authorization code with PKCE Flow.

darrenjrobinson commented 2 years ago

I should also mention that I also tried obtaining an Authorization Code and then using the -AuthorizationCode option in Get-MsalToken to get the access token.

Example

$msalParams = @{
    grant_type    = "authorization_code";
    code_verifier = $pkceCodes.code_verifier;
}

Import-Module MSAL.PS 
$clientSecretEncoded = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force
Get-MsalToken -ClientId $clientID `
    -ClientSecret $clientSecretEncoded `
    -AuthorizationCode $authCode `
    -TenantId $tenantID `
    -RedirectUri $replyURL `
    -ExtraQueryParameters $msalParams `
    -Authority "https://login.microsoftonline.com/$($tenantID)/oauth2/v2.0/token" `
    -Verbose -Debug

The error returned is:

Get-MsalToken: 
Line |
   2 |  Get-MsalToken -ClientId $clientID `
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | AADSTS50148: The code_verifier does not match the code_challenge supplied in the authorization request for PKCE.    
Trace ID: 785ac559-cda4-4c2c-8297-e67f053d1b00
Correlation ID: 6eca98e6-47d2-4bac-ab73-7dda251cd481
Timestamp: 2021-12-22 04:49:33Z

The code_verifier I have generated is correct which I verified by doing a POST call to "https://login.microsoftonline.com/$($tenantID)/oauth2/v2.0/token" with it and getting an access token.

I also tried resending the code_challenge in case the code_challenge_method is set to plain. But it failed with the same error.

MSAL.PS Version:

Script     4.37.0.0              MSAL.PS                   
SCOMnewbie commented 2 years ago

Hi, I've tried to reproduce your issue but before going exactly your scenario where you try to generate an access toekn for a confidential app, I've started with a public one. Here what I've done: New app registration, V2 endpoint enforced, allow public client true, redirecturi localhost, no secret.

Then I've started Fiddler and run in the Pwsh console: Get-MsalToken -ClientId $clientId -TenantId $TenantId -RedirectUri $RedirectURI -Scopes $scopes -ForceRefresh

On Fiddler side, we can see the command executed is: https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/authorize?scope=openid+offline_access+User.Read &response_type=code &client_id=<clientId> &redirect_uri=http%3A%2F%2Flocalhost%2F &client-request-id=8b1fcba1-725a-415c-b761-b61c4fff3795 &x-client-SKU=MSAL.NetCore &x-client-Ver=4.37.0.0 &x-client-CPU=x64 &x-client-OS=Microsoft+Windows+10.0.19042 &prompt=select_account &code_challenge=XRu4k8rduOKHB....kxWd8vI6s4rgjU &code_challenge_method=S256 &state=e837e472-4f90-403d-9c2f-bfb360ee82632ebb39b3-72a5-4488-9632-b8f9964b8ae4 &client_info=1 HTTP/1.1

As you can see, auth code flow with PKCE (code_challenge_method=S256) seems to be the default behavior.

I will continue my investigation

SCOMnewbie commented 2 years ago

After a little bit of reverse engineering, I have to admit this is not obvious. But to be honest, trying to do a confidential app (because now you have a secret) from a PS console does not make any sense, or I don't understand the use case :p. You should use the publicclientapp (see above) in this case. Anyway here what I've done to make it work:

Create a new app, V2 endpoint, kept the allow public client at false this time, native client redirect uri with localhost and of course a secret.

Now to get a token, you can do:

$clientId = "02b3de1e-0047-4d53-9a9e-a58932800003" $TenantId = "<tenantId>" $RedirectURI = "http://localhost" $ExtraQueryParameters = @{} $ExtraQueryParameters.Add('client_secret','<your secret in cleartext>') $tokenInfo = Get-MsalToken -Interactive -ClientId $clientId -TenantId $TenantId -RedirectUri $RedirectURI -ExtraQueryParameters $ExtraQueryParameters

And that's it, you should now get your Id/access/refresh token.

But again, if you plan to use this semi-interractive (because of Refresh token) flow, I think using 2 apps, one public (your Pwsh console) which call your backend (second app) should be a better option.

BTW, this is what I get from Fiddler:

image

I hope it will help you. Cheers

darrenjrobinson commented 2 years ago

Thx for looking into this. I can see from your Fiddler trace what I'm expecting and trying to achieve. However, I'm not progressing successfully.

In your last example you show getting a token without specifying PKCE yet your Fiddler trace shows the code_challenge. I've tried adding in the code_challenge to $ExtraQueryParameters but Get-MsalToken fails with

Get-MsalToken: 
Line |
  14 |  $tokenInfo = Get-MsalToken -Interactive `
     |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Duplicate query parameter 'code_challenge' in extraQueryParameters.

How did you send code_challenge in the authcode request?

SCOMnewbie commented 2 years ago

Nothing in my sleeves :p. It seems to be the default behavior. Here what I've done:

image

Again, my app registration is a confidential one, and redirect URI set as a native client. Sonce the last post, I've changed the redirecturi from localhost to msal.. just for testing.

Now here the 2 interresting calls. The first one with the code response_type:

image

And the back channel one with the verifier:

image

So as you can see, I don't need any custom code, the MSAL lib seems to manage everything on his own.

Once you get the token, you can use the -silent insted of the -interrctive instead to use the cache.

darrenjrobinson commented 2 years ago

That makes sense if the MSAL.PS doing it by default. It explains the duplicate entries for values when I was adding them via Extra Parameters. Thanks for your help, much appreciated.

jazuntee commented 2 years ago

Yeah, MSAL.net always uses PKCE with auth code flow. https://docs.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code

Idlefisher commented 2 years ago

That makes sense if the MSAL.PS doing it by default. It explains the duplicate entries for values when I was adding them via Extra Parameters. Thanks for your help, much appreciated.

Hi Darren,

I'm also trying to leverage MSAL.PS to emulate a confidential client with the auth code flow. However, MSAL.PS seems to be wrapping the client_secret parameter in the query string while it is actually expected in the body. Thus the token endpoint always responses with "The request body must contain the following parameter: 'client_assertion' or 'client_secret'."

Have you managed to get through this?

Thanks.