Do not input private or sensitive data. View Qlik Privacy & Cookie Policy.
Skip to main content

Announcements
Qlik and ServiceNow Partner to Bring Trusted Enterprise Context into AI-Powered Workflows. Learn More!
cancel
Showing results for 
Search instead for 
Did you mean: 
AyeshaChhapra
Contributor II
Contributor II

OAUTH-5 401 Unauthorized on /oauth/token with PKCE Exchange

 

Hi Qlik Community,

I’m working on implementing OAuth2 Authorization Code Flow with PKCE in a React SPA for Qlik Cloud, but I’m running into a 401 error when attempting to exchange my authorization code for tokens.

 

Error Response

When I POST to /oauth/token, I receive:

{
"errors": [
{
"title": "Unauthorized",
"code": "OAUTH-5",
"status": "401"
}
],
"traceId": "e6f75ad8bb6ac27f4c5992feb440d713"
}

 

Code Snippet

Here is my token-exchange function using axios:

 async function fetchTokens() {
      const params = new URLSearchParams(search);
      const code = params.get("code");
      const verifier = sessionStorage.getItem("pkce_verifier");

      const body = new URLSearchParams({
        grant_type: "authorization_code",
        client_id: import.meta.env.VITE_QLIK_CLIENT_ID,
        code_verifier: verifier,
        code,
        redirect_uri: import.meta.env.VITE_REDIRECT_URI,
      });
      console.log({
        grant_type: "authorization_code",
        client_id: import.meta.env.VITE_QLIK_CLIENT_ID,
        code_verifier: verifier,
        code,
        redirect_uri: import.meta.env.VITE_REDIRECT_URI,
      });
      const response = await fetch(
        `${import.meta.env.VITE_TENANT_URI}/oauth/token`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
          body,
        }
      );
      const data = await response.json();
      setAuthTokens(data);
      navigate("/", { replace: true });
    }
Labels (4)
1 Solution

Accepted Solutions
alex_colombo
Employee
Employee

Hey @AyeshaChhapra , I'm seeing two missing things here.

First, which could not cause the issue, you are missing "scope" parameter in the /oauth/token body request. You can use "user_default", but it depends on your requirements.

Second, which could cause the issue, you are missing "credentaials": "include" in the /oauth/token request. When you are redirected you should have some technical cookies and have to be attached.

Below my code which is working correctly:

const oAuthTokenRequestBody = {
            "grant_type": "authorization_code",
            "scope": "user_default",
            "code": oAuthCode,
            "redirect_uri": "https://localhost:8080/oauth_callback_custom.html",
            "code_verifier": localStorage.getItem('codeVerifier'),
            "client_id": "_clientId_"
        }
        const oAuthTokenRequest = await fetch('https://_tenant_.qlikcloud.com/oauth/token', {
          method: 'POST',
          body: JSON.stringify(oAuthTokenRequestBody),
          credentials: "include",
          headers: {
            "content-type": "application/json"
          }
        })

View solution in original post

2 Replies
daisy64
Contributor
Contributor

Hi there!

A 401 "Unauthorized" error (code "OAUTH-5") during the token exchange step of the OAuth2 Authorization Code Flow with PKCE typically indicates an issue with the credentials or parameters being sent to the /oauth/token endpoint. Given your code snippet and the error, here's a breakdown of common causes and how to troubleshoot them:

Common Causes for OAUTH-5 (401 Unauthorized)
Incorrect client_id: This is a very frequent culprit. Double-check that import.meta.env.VITE_QLIK_CLIENT_ID exactly matches the Client ID you obtained when registering your SPA in Qlik Cloud. Any typo or mismatch will lead to a 401.

Mismatched redirect_uri: The redirect_uri sent in the token exchange request (import.meta.env.VITE_REDIRECT_URI) must precisely match the redirect_uri that was used in the initial authorization request and, crucially, the redirect_uri configured for your client in Qlik Cloud. Even a trailing slash difference can cause this error.

Invalid code: The authorization code you received from the authorization server is single-use and has a short lifespan.

Expired Code: If there's a significant delay between getting the code and exchanging it, it might have expired.

Used Code: If you're trying to exchange the same code multiple times (e.g., due to a page refresh or re-rendering), it will be invalid after the first successful exchange.

Mismatched code_verifier: The code_verifier sent in the token exchange must exactly match the original code_verifier that was used to generate the code_challenge for the initial authorization request.

Storage Issue: Ensure sessionStorage.getItem("pkce_verifier") is correctly retrieving the exact verifier that was stored. Any modification, truncation, or failure to retrieve it will cause a mismatch.

Timing: The verifier should be stored before redirecting to the authorization endpoint.

Incorrect Content-Type Header: While your code snippet correctly sets "Content-Type": "application/x-www-form-urlencoded", it's worth double-checking that no other part of your application or any proxy is inadvertently changing this. The body being a URLSearchParams object is correct for this content type.

Qlik Cloud Application Configuration:

PKCE Enabled: Ensure that your OAuth client in Qlik Cloud is configured to use PKCE. While the code_verifier implies you're attempting to use it, the server-side configuration needs to align.
marykayintouch

Grant Types: Verify that "Authorization Code" is an allowed grant type for your client in Qlik Cloud.

alex_colombo
Employee
Employee

Hey @AyeshaChhapra , I'm seeing two missing things here.

First, which could not cause the issue, you are missing "scope" parameter in the /oauth/token body request. You can use "user_default", but it depends on your requirements.

Second, which could cause the issue, you are missing "credentaials": "include" in the /oauth/token request. When you are redirected you should have some technical cookies and have to be attached.

Below my code which is working correctly:

const oAuthTokenRequestBody = {
            "grant_type": "authorization_code",
            "scope": "user_default",
            "code": oAuthCode,
            "redirect_uri": "https://localhost:8080/oauth_callback_custom.html",
            "code_verifier": localStorage.getItem('codeVerifier'),
            "client_id": "_clientId_"
        }
        const oAuthTokenRequest = await fetch('https://_tenant_.qlikcloud.com/oauth/token', {
          method: 'POST',
          body: JSON.stringify(oAuthTokenRequestBody),
          credentials: "include",
          headers: {
            "content-type": "application/json"
          }
        })