Reputation: 2195
I have been trying to add the state
and code_challenge
to our flow but for some reason, I continue to get invalid_request
responses from Amazon.
I followed this Auth0 tutorial to a tee.
/**
* Converts buffer to Base64 URL encoded string
*
* @param {Buffer} buf The buffer to convert
* @returns {string}
*/
private base64URLEncode(str: any): string {
return str.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
/**
* Generates a new code challenge
*
* @returns {string} The code challange string
*/
private generateCodeChallenge(): string {
if (!this._verifier) this._verifier = this.base64URLEncode(crypto.randomBytes(32));
return this.base64URLEncode(crypto.createHash('sha256').update(this._verifier).digest());
}
/**
* Generates the Authorization URL
*/
public generateAuthUrl(): string {
if (!this.config.clientId) throw new Error('Client ID is missing from configuration.');
if (!this.config.redirectUrl) throw new Error('Redirect URI is missing from configuration.');
if (!this._state) this._state = this.base64URLEncode(crypto.randomBytes(28));
return `${this.config.protocol}://signin.${
this.config.host
}/login?response_type=code&client_id=${this.config.clientId}&redirect_uri=${
this.config.redirectUrl
}&scope=${this._scope.join('%20')}&state=${
this._state
}&code_challenge_method=S256&code_challenge=${this.generateCodeChallenge()}`;
}
/**
* Verifies that the state matches
*
* @returns {boolean}
*/
public verifyState(state: string): boolean {
return this._state === state;
}
/**
* Retrieves a new OAuth Authorization Grant token
*/
public async getToken(code: string): Promise<UserAccessToken> {
if (!this.config.clientId) throw new Error('Client ID is missing from configuration.');
if (!this.config.redirectUrl) throw new Error('Redirect URI is missing from configuration.');
try {
const token = (await this.req.postform(
`/oauth2/token`,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-store',
},
},
{
grant_type: 'authorization_code',
code,
client_id: this.config.clientId,
code_verifier: this._verifier,
redirect_uri: this.config.redirectUrl,
}
)) as UserAccessToken;
this._userAccessToken = token;
return token;
} catch (err) {
throw err;
}
}
Everything is working well up until exchanging a code for a token. We redirect to our hosted UI and successfully get a code back. When we exchange that for a token, no matter what I have tried, we get an invalid_request
response. If I remove the code challenge, everything works as expected so I am pretty confident that it's what is causing the issue.
This function is called in our React app inside of a useEffect
hook like so:
useEffect(() => {
const authUrl = sdk.auth.generateAuthUrl();
console.log(authUrl);
if (!code) window.location.assign(authUrl);
console.log(`New Auth URL: ${authUrl}`);
sdk.auth
.getToken(code as string)
.then((token) => {
localStorage.setItem('sdk_access_token', JSON.stringify(token));
sdk.organizations.getUser().then((user) => {
setUser({ ...user });
history.push('/');
});
})
.catch((err) => console.log(err));
}, []);
Both console.log
statements show the same URL:
But on the Network debugging tab, the request URL is different:
How is that possible? Does window.location.href
somehow encode the URL?
Upvotes: 1
Views: 2345
Reputation: 29273
Your code and messages look fine as far as I can see. If you have an App Client Secret configured in Cognito that would explain the problem (Cognito error responses are not the best).
Maybe try these hard coded values from a working Cognito sample of mine - or run the values you are calculating through this tool. This will enable you to know whether the problem is with the PKCE values:
There are plenty of libraries with some working PKCE code though I expect the Auth0 code is fine ...
Upvotes: 2