Introspect endpoint issue: "Invalid client_id"

I’m trying to get a backend Express API to validate access tokens passed to it by the frontend of my app. Here’s the facts:

  • My frontend is configured as an SPA, so there is no client secret. Access tokens are generated by the okta-vue package.
  • My org is still on Okta Classic, and does not have API AM enabled. For that reason, I cannot use okta-jwt-verifier (which requires an auth server to exist in some form), and wanted to use the /introspect endpoint instead.

However, no matter what I do with /introspect, I can’t get further than the error “Invalid value for ‘client_id’ parameter.”

  const url = "https://subdomain.okta.com/oauth2/v1/introspect";

  const resp = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization:
        "Basic " + Buffer.from("<username>:<password>").toString("base64"),
        // what is the purpose of this line? nothing is mentioned in the documentation
    },
    body: JSON.stringify({
      client_id: process.env.OKTA_CLIENTID,
      // The client_id is *still* not mentioned in the parameter list in the docs for either
      // introspect endpoint, by the way
      token: token,
      token_type_hint: "access_token",
    }),
  });

My worry is, when I look at the .well-known/openid-configuration?client_id=my_client_id, the introspection_endpoint_auth_methods_supported field shows ["none"]. Does this mean that the /introspect endpoint is not supported for my app, which is what I’m assuming from “none?” Is there somewhere my Okta admin can enable private_key_jwt as an option for /introspect, since every other option seems to imply my app needs a client_secret to function? (Also, what are the differences between the auth_methods_supported, since none are described in the documentation? Even a link somewhere would help)

(All introspection_endpoint_auth_methods_supported, for reference: “client_secret_basic” “client_secret_jwt” “client_secret_post” “none” “private_key_jwt”)

Edit: I found the Client authentication methods article, which explains the auth methods, including it here in case the link helps others. Turns out “none” is the only option for an SPA, so I’m still confused as to why my attempts at the /introspect endpoint are failing.

  1. For SPAs using PKCE, you don’t need a client secret. The “none” value in the introspection_endpoint_auth_methods_supported field is correct for your use case.
  2. To use the /introspect endpoint with a public client (like your SPA), you need to modify your request slightly:
const url = "https://subdomain.okta.com/oauth2/v1/introspect";

const params = new URLSearchParams();
params.append('token', token);
params.append('token_type_hint', 'access_token');
params.append('client_id', process.env.OKTA_CLIENTID);

const resp = await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: params
});

Changes:

  • Remove the Authorization header
  • Use application/x-www-form-urlencoded content type
  • Include client_id in the request body
  • Use URLSearchParams to properly encode the body
3 Likes

@SitaRam, the relief I experienced when this suddenly, immediately, worked was immense. Thank you a hundred times over.

I wish the docs for /introspect included this (or at least more information on formatting the request when the app is not configured to use a client secret), but hopefully this thread helps others in the future.

2 Likes

Glad it worked. Cheers.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.