Select to view content in your preferred language

IOAuthAuthorizeHandler Throws "Invalid PKCE exception" even though the token request succeeds

655
6
Jump to solution
02-12-2026 03:49 PM
Labels (5)
EricLiu
Occasional Contributor

Hi, I'm calling OAuthUserCredential.CreateAsync() in a try / catch block. This call gets routed to my own implementation of OAuthAuthorizeHandler.AuthorizeAsync().

From the authorizeUri parameter of AuthorizeAsync(), I can see the SDK passed in a "code_challenge" hash string. But since I don't know the verifier, I have to replace the challenge with my own for PKCE.

I generated a pair of "code_challenge" and "code_verifier" strings (Using default Base64Url.EncodeToString(), SHA256.Create()). I sent the "code_challenge" string in my OAuth2 authorization request. The "code_challenge_method" is "S256". This completes successfully and I get a code.

Then in my request to "https://www.arcgis.com/sharing/rest/oauth2/token, I have "code_verifier" set to the one I generated. I have "grant_type" set to "authorization_code". Along with other required parameters.

The response comes back with Status 200, OK. Using a JSON De-serializer on the response stream, I was able to extract the username, access token, refresh token ... etc. I then add these to a dictionary and return the dictionary in AuthorizeAsync(). 

After exiting from AuthorizeAsync(), the code went into my catch block (even though there was no exceptions in AuthorizeAsync()). The error was {"error":{"code":400,"error":"invalid_request","error_description":"Invalid PKCE code_challenge_verifier","message":"Invalid PKCE code_challenge_verifier","details":[]}}.

Is this a bug ? Or was I supposed to use the code_challenge provided by the SDK (if so how would I know the code_verifier). 

.NET 9.0.302, Maui 9.0.100, ArcGIS Runtime 200.7 (also happens 200.8)

 

 

0 Kudos
1 Solution

Accepted Solutions
MatveiStefarov
Esri Contributor

Hello Eric!  I see the issue. You are seeing that error because the OAuthUserCredential workflow is designed to handle the PKCE exchange and Token request for you automatically. There is no need to manually post anything to /rest/oauth2/token.

When you implement IOAuthAuthorizeHandler, your only responsibility is to:

  1. Launch the browser (or system web view) using the exact authorizeUri passed into the method (it already contains the correct code_challenge).
  2. Listen for the redirect / protocol activation.
  3. Return the "code" parameter from the redirect URI.

Once it has the "code", OAuthUserCredential can make an "authorization_code" request itself and retrieve all other information: username, access token, refresh token, etc.

You can take advantage of MAUI WebAuthenticator on iOS/Android or OAuth2Manager for WinUI to simplify the implementation of your authorization handler.

View solution in original post

6 Replies
EricLiu
Occasional Contributor

Btw, is there documentation on what key / value pairs should be included in the result of AuthorizeAsync() ?

I've put in code, access_token, refresh_token, expires_in, username, ssl, refresh_token_expires_in. I also tried adding code_challenge and code_verifier but that did not fix my issue.

If I don't include the "code" kvp in my output, AuthorizeAsync() fails with missing authorization code. This wasn't required when using GenerateCredentialAsync() before (I've replaced it with OAuthUserCredential.CreateAsync()). 

 

0 Kudos
MatveiStefarov
Esri Contributor

Hello Eric!  I see the issue. You are seeing that error because the OAuthUserCredential workflow is designed to handle the PKCE exchange and Token request for you automatically. There is no need to manually post anything to /rest/oauth2/token.

When you implement IOAuthAuthorizeHandler, your only responsibility is to:

  1. Launch the browser (or system web view) using the exact authorizeUri passed into the method (it already contains the correct code_challenge).
  2. Listen for the redirect / protocol activation.
  3. Return the "code" parameter from the redirect URI.

Once it has the "code", OAuthUserCredential can make an "authorization_code" request itself and retrieve all other information: username, access token, refresh token, etc.

You can take advantage of MAUI WebAuthenticator on iOS/Android or OAuth2Manager for WinUI to simplify the implementation of your authorization handler.

EricLiu
Occasional Contributor

Thank you Matvei, that worked! I believe exchanging the token manually allowed us to specify a NetworkCredential in our HttpClientHandler before making the request. I suppose if I want to do that now I have to inject the NetworkCredential somehow into the HttpRequest in IHttpMessageInterceptor.

0 Kudos
MatveiStefarov
Esri Contributor

I hope that you will find working with network credentials easier than expected.

If you know the network credentials ahead of time, you can create an ArcGISNetworkCredential and add it to the AuthenticationManager before creating the OAuth credential.

Or, to prompt the user for network credentials as needed, implement a ChallengeHandler and set it on the AuthenticationManager. Your handler will be called when a service prompts for login, and you can create/return the appropriate credential based on given ServiceUri and AuthenticationType.

The only thing Maps SDK cannot help with is -- if loading the OAuth "authorize" URL has multiple layers of security, the browser will have to prompt for these credentials separately from the app.

0 Kudos
EricLiu
Occasional Contributor

@MatveiStefarov I have a portal with IWA enabled. On Windows, it uses my IWA credentials and takes me directly to the OAuth2 page (via AuthorizeAsync), then I can sign in successfully. On iOS I can generate an ArcGISNetworkCredential (via username + password) and add it to the AuthenticationManager. After that I get an OAuth2 challenge and I can sign in like Windows.

But on Android, it hangs. I can generate & save the ArcGISNetworkCredential just fine like on iOS. But then when I call OAuthUserCredential.CreateAsync(), it takes me to AuthorizeAsync. I exit AuthorizeAsync successfully with the OAuth2 authorization code in the dictionary result; but it never exits to the line after OAuthUserCredential.CreateAsync() (doesn't get caught either).

If I manually exchange the OAuth authorization code for the access token (with the ArcGISNetworkCredential added to the HttpHandler making the request), I get a successful response + token. But it also hangs after returning from AuthorizeAsync() (ArcGIS is probably also attempting to exchange for the token and getting stuck).

For what its worth internally we have an old comment "_authMan.GetCredentialAsync hangs on Android when using IWA without anonymous access ...", so I wonder if we've known about an Android issue for a while.

Possibly related to the PreAuthenticate setting (on the HttpClientHandler or the ArcGISNetworkCredential) ?

Could you give me some pointers on why this is happening ?

 

0 Kudos
EricLiu
Occasional Contributor

To add, this seems like a Maui / .NET bug that the ArcGIS runtime https://github.com/dotnet/android/issues/8379 is affected by. I guess I can write to the header of the request myself but it seems a bit sketchy. Some official Esri guidance needed 😛

0 Kudos