I figured out this error in my case, and just want to document it.
I need to implement OAuth 2, specifically the Authorization Code Flow and the Refreshing an Access Token Flow. First, I created a React public client, and ran a default KC instance, acting as my authorization server. The AuthZ Code Flow worked fine, and I received all the necessary data, including an access token and a refresh token.
To perform the next flow, I sent a POST request to KC's endpoint /token
with the urlencoded body as grant_type=refresh_token;client_id=clientIdHere;refresh_token=refreshTokenHere
, as instructed in here. For some reason, KC refused and returned an error like this:
{
"error": "invalid_grant",
"error_description": "Session doesn't have required client"
}
# running the default KC instance
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
# useEffect code
const navigate = useNavigate();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const authZCode = params.get("code");
const fetchDataToLS = async (codeHere: string) => {
const tokenRes = await axios.post<TKCTokenResponse>(
import.meta.env.VITE_KC_SERVER_TOKEN,
new URLSearchParams({
client_id: "myclient",
code: codeHere,
grant_type: "authorization_code",
})
);
const { access_token, refresh_token, id_token } = tokenRes.data;
localStorage.setItem("access_token", access_token);
localStorage.setItem("refresh_token", refresh_token);
localStorage.setItem("id_token", id_token);
navigate("/protected");
};
fetchDataToLS(authZCode!);
callTime++;
}, [callTime, navigate]);
From what I could gather, there are 2 main reasons causing this error:
useEffect
, which will make a second run if in React Strict ModeAs I was testing KC, my code was still in development mode, hence the double run from useEffect
, which are captured like this, regardless of the order of those requests. And because React used the same authZ code a 2nd time, which might be the main reason, making KC denied and revoked that code, thus the refresh token associated with that authZ code would not be valid anymore.
To fix this, you could try to add logic so that the exchange should be run one time only, though I'm not sure this is an optimal solution.