props.onNext(3)}
+>
+
+
+ In the following steps, we assume your app is running on http://localhost:3000
.
+
+
+### Configure Redirect URI
+
+First, let’s enter your redirect URI. E.g. `http://localhost:3000/callback`.
+
+
+
+Go back to your IDE/editor, we need to implement the following authenticate functions.
+
+### Implement authenticate functions
+
+1. `getSignInUrl`: builds and returns a complete URL of the Logto Authorization Server to which users will be redirected.
+2. `handleSignIn`: parses the callback URL after the authentication process completes, gets the code query parameter, and then fetches tokens (an access token, the refresh token, and an ID token) to complete the sign in process.
+3. `refreshTokens`: exchanges a new access token using the refresh token.
+
+
+
+{`// logto.js
+
+const {
+ withReservedScopes,
+ fetchOidcConfig,
+ discoveryPath,
+ createRequester,
+ generateSignInUri,
+ verifyAndParseCodeFromCallbackUri,
+ fetchTokenByAuthorizationCode,
+ fetchTokenByRefreshToken,
+} = require('@logto/js');
+const fetch = require('node-fetch');
+const { randomFillSync, createHash } = require('crypto');
+const { fromUint8Array } = require('js-base64');
+
+const config = {
+ endpoint: '${props.endpoint}',
+ appId: '${props.appId}',
+ redirectUri: 'http://localhost:3000/callback', // Configured in the previous step
+ scopes: withReservedScopes().split(' '),
+};
+
+const requester = createRequester(fetch);
+
+const generateRandomString = (length = 64) => {
+ return fromUint8Array(randomFillSync(new Uint8Array(length)), true);
+};
+
+const generateCodeChallenge = async (codeVerifier) => {
+ const encodedCodeVerifier = new TextEncoder().encode(codeVerifier);
+ const hash = createHash('sha256');
+ hash.update(encodedCodeVerifier);
+ const codeChallenge = hash.digest();
+ return fromUint8Array(codeChallenge, true);
+};
+
+const getOidcConfig = async () => {
+ return fetchOidcConfig(new URL(discoveryPath, config.endpoint).toString(), requester);
+};
+
+exports.getSignInUrl = async () => {
+ const { authorizationEndpoint } = await getOidcConfig();
+ const codeVerifier = generateRandomString();
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
+ const state = generateRandomString();
+
+ const { redirectUri, scopes, appId: clientId } = config;
+
+ const signInUri = generateSignInUri({
+ authorizationEndpoint,
+ clientId,
+ redirectUri: redirectUri,
+ codeChallenge,
+ state,
+ scopes,
+ });
+
+ return { redirectUri, codeVerifier, state, signInUri };
+};
+
+exports.handleSignIn = async (signInSession, callbackUri) => {
+ const { redirectUri, state, codeVerifier } = signInSession;
+ const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
+
+ const { appId: clientId } = config;
+ const { tokenEndpoint } = await getOidcConfig();
+ const codeTokenResponse = await fetchTokenByAuthorizationCode(
+ {
+ clientId,
+ tokenEndpoint,
+ redirectUri,
+ codeVerifier,
+ code,
+ },
+ requester
+ );
+
+ return codeTokenResponse;
+};
+
+exports.refreshTokens = async (refreshToken) => {
+ const { appId: clientId, scopes } = config;
+ const { tokenEndpoint } = await getOidcConfig();
+ const tokenResponse = await fetchTokenByRefreshToken(
+ {
+ clientId,
+ tokenEndpoint,
+ refreshToken,
+ scopes,
+ },
+ requester
+ );
+
+ return tokenResponse;
+};`}
+
+
+
+
+
+ props.onNext(3)}
+>
+
+
+ 在如下代码示例中, 我们均先假设你的 React 应用运行在 http://localhost:3000
上。
+
+
+### 配置 Redirect URI
+
+首先,我们来添加 Redirect URI,如:`http://localhost:3000/callback`。
+
+
+
+返回你的 IDE 或编辑器,我们将会实现如下几个用户认证所需函数。
+
+### 实现用户认证的函数
+
+1. `getSignInUrl`: 构建并返回完整的用于 Logto 认证服务的 URL,用户将被重定向到这个 URL 以完成登录。
+2. `handleSignIn`: 解析回调 URL, 从 query 参数中获取 code, 并用它获取其他 token (an access token, the refresh token, and an ID token),完成整个登录流程。
+3. `refreshTokens`: 使用 refresh token 获取新的 access token。
+
+
+
+{`// logto.js
+
+const {
+ withReservedScopes,
+ fetchOidcConfig,
+ discoveryPath,
+ createRequester,
+ generateSignInUri,
+ verifyAndParseCodeFromCallbackUri,
+ fetchTokenByAuthorizationCode,
+ fetchTokenByRefreshToken,
+} = require('@logto/js');
+const fetch = require('node-fetch');
+const { randomFillSync, createHash } = require('crypto');
+const { fromUint8Array } = require('js-base64');
+
+const config = {
+ endpoint: '${props.endpoint}',
+ appId: '${props.appId}',
+ redirectUri: 'http://localhost:3000/callback', // 上一步配置过的 Redirect URI
+ scopes: withReservedScopes().split(' '),
+};
+
+const requester = createRequester(fetch);
+
+const generateRandomString = (length = 64) => {
+ return fromUint8Array(randomFillSync(new Uint8Array(length)), true);
+};
+
+const generateCodeChallenge = async (codeVerifier) => {
+ const encodedCodeVerifier = new TextEncoder().encode(codeVerifier);
+ const hash = createHash('sha256');
+ hash.update(encodedCodeVerifier);
+ const codeChallenge = hash.digest();
+ return fromUint8Array(codeChallenge, true);
+};
+
+const getOidcConfig = async () => {
+ return fetchOidcConfig(new URL(discoveryPath, config.endpoint).toString(), requester);
+};
+
+exports.getSignInUrl = async () => {
+ const { authorizationEndpoint } = await getOidcConfig();
+ const codeVerifier = generateRandomString();
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
+ const state = generateRandomString();
+
+ const { redirectUri, scopes, appId: clientId } = config;
+
+ const signInUri = generateSignInUri({
+ authorizationEndpoint,
+ clientId,
+ redirectUri: redirectUri,
+ codeChallenge,
+ state,
+ scopes,
+ });
+
+ return { redirectUri, codeVerifier, state, signInUri };
+};
+
+exports.handleSignIn = async (signInSession, callbackUri) => {
+ const { redirectUri, state, codeVerifier } = signInSession;
+ const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
+
+ const { appId: clientId } = config;
+ const { tokenEndpoint } = await getOidcConfig();
+ const codeTokenResponse = await fetchTokenByAuthorizationCode(
+ {
+ clientId,
+ tokenEndpoint,
+ redirectUri,
+ codeVerifier,
+ code,
+ },
+ requester
+ );
+
+ return codeTokenResponse;
+};
+
+exports.refreshTokens = async (refreshToken) => {
+ const { appId: clientId, scopes } = config;
+ const { tokenEndpoint } = await getOidcConfig();
+ const tokenResponse = await fetchTokenByRefreshToken(
+ {
+ clientId,
+ tokenEndpoint,
+ refreshToken,
+ scopes,
+ },
+ requester
+ );
+
+ return tokenResponse;
+};`}
+
+
+
+
+
+