如何用React和Doorkeeper实现PKCE OAuth2



我使用Devise和Doorkeeper创建了一个Rails应用程序,用于身份验证,用作React Native应用程序的API和OAuth2 PKCE

预期行为:

当我发送带有在PKCE Flow 的步骤B中接收到的代码的POST请求时,返回身份验证令牌

实际行为

它返回此错误而不是令牌:

{
"error": "invalid_grant",
"error_description": "The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.",
}

如何重现错误:

  • 对于React Native应用程序:
  1. expo init auth-pkce-app->选择Typescrit空白模板

  2. expo install expo-linking expo-web-browser js-sha256

  3. 然后将App.tsx中的代码替换为:

import React, { useEffect, useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import * as Linking from "expo-linking";
import * as WebBrowser from "expo-web-browser";
import { sha256 } from "js-sha256";
const CLIENT_ID = "Ox8_PbOB3g9kBy1HsuEWm6ieePS35jQWcaP_a2D6EmU";
const CLIENT_SECRET = "Z2TJo0AZeseyVvpua7piCPTBXA2v2pIAI3aBKpP1n8c";
const CODE_VERIFIER = "Antante";
const code_challenge = sha256(CODE_VERIFIER);
const code_chanllenge_method = "S256";
const AUTHORIZE_URL =
`http://localhost:3000/oauth/authorize` +
`?client_id=${CLIENT_ID}` +
`&redirect_uri=${Linking.createURL("")}` +
`&response_type=code` +
`&scope=write` +
`&code_challenge=${code_challenge}` +
`&code_challenge_method=${code_chanllenge_method}`;

const TOKEN_URL =
"http://localhost:3000/oauth/token" +
`?client_id=${CLIENT_ID}` +
`&client_secret=${CLIENT_SECRET}` +
"&grant_type=authorization_code" +
`&code_verifier=${CODE_VERIFIER}` +
`&redirect_uri=${Linking.createURL("")}`;
const App: React.FC<{}> = () => {
const [state, setState] = useState<{
redirectData?: Linking.ParsedURL | null;
result?: WebBrowser.WebBrowserAuthSessionResult;
}>({
redirectData: null,
});
useEffect(() => {
fetchToken();
}, [state]);
const fetchToken = async () => {
try {
const response = await fetch(
TOKEN_URL + `&code=${state.redirectData?.queryParams.code}`,
{
method: "POST",
}
);
const data = await response.json();
console.log(data);
} catch (err) {
console.log(err);
}
};
const openAuthSessionAsync = async () => {
try {
let result = await WebBrowser.openAuthSessionAsync(
AUTHORIZE_URL,
Linking.createURL("")
);
let redirectData;
if (result.type === "success") {
redirectData = Linking.parse(result.url);
}
setState({ result, redirectData });
} catch (error) {
alert(error);
console.log(error);
}
};
const maybeRenderRedirectData = () => {
if (!state.redirectData) {
return;
}
console.log(state.redirectData);
return (
<Text style={{ marginTop: 30 }}>
{JSON.stringify(state.redirectData)}
</Text>
);
};
return (
<View style={styles.container}>
<Button onPress={openAuthSessionAsync} title="Go to login" />
<Text>{Linking.createURL("")}</Text>
{maybeRenderRedirectData()}
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
paddingBottom: 40,
},
header: {
fontSize: 25,
marginBottom: 25,
},
});

  1. 使用yarn start和<lt;请记住标签为"转到登录"的按钮下的重定向链接>gt
  • 对于带有Doorkeeper的Rails应用程序
  1. git clone git@github.com:dogaruemiliano/pkce-auth.git rails-pkce-auth

  2. bundle install

  3. 转到db/seeds.rb,将文件顶部的链接(REDIRECT_URI = 'exp://192.168.0.107:19000')替换为我们上面讨论过的链接。

  4. rails db:migrate db:seed

^这将在终端中输出Doorkeeper::应用程序详细信息(id,secret)

  1. rails g webpacker:install

  2. 轨道的

问题在于sha256编码。在您的示例中,从js-sha256返回的是基于十六进制的,但我们需要匹配doorkeeper中的urlsafe base64编码的sha256:

def generate_code_challenge(code_verifier)
Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier), padding: false)
end

使用expo-crypto(例如)可能更容易实现这一点

var code_challenge = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
CODE_VERIFIER,
{encoding: Crypto.CryptoEncoding.BASE64}
).then(
digest => digest.replace(/+/g, '-').replace(///g, '_').replace(/=+$/, '')
)

希望能有所帮助。

最新更新