diff --git a/README.md b/README.md index 6ee26e8..b5f91b5 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ Initialization requires 7 parameters, which are all string type: | ---------------- | ---- | --------------------------------------------------- | | serverUrl | Yes | your Casdoor server URL | | clientId | Yes | the Client ID of your Casdoor application| -|clientSecret|Yes|the Client Secret of your Casdoor application| | appName | Yes | the name of your Casdoor application | | organizationName | Yes | the name of the Casdoor organization connected with your Casdoor application | | redirectPath | No | the path of the redirect URL for your Casdoor application, will be `/callback` if not provided | @@ -53,7 +52,6 @@ import SDK from 'casdoor-react-native-sdk' const sdkConfig = { serverUrl: 'https://door.casdoor.com', clientId: 'b800a86702dd4d29ec4d', - clientSecret: '1219843a8db4695155699be3a67f10796f2ec1d5', appName: 'app-example', organizationName: 'casbin', redirectPath: 'http://localhost:5000/callback', @@ -75,7 +73,6 @@ Initialization parameters are consistent with the previous node.js section: const sdkConfig = { serverUrl: 'https://door.casdoor.com', clientId: 'b800a86702dd4d29ec4d', - clientSecret: '1219843a8db4695155699be3a67f10796f2ec1d5', appName: 'app-example', organizationName: 'casbin', redirectPath: 'http://localhost:5000/callback', diff --git a/package.json b/package.json index e74cc85..c10fedd 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "homepage": "https://github.com/casdoor/casdoor-react-native-sdk", "dependencies": { "@react-native-async-storage/async-storage": "^1.19.3", - "jwt-decode": "^3.1.2" + "jwt-decode": "^3.1.2", + "react-native-pkce-challenge": "^5.2.0" } } diff --git a/src/sdk.ts b/src/sdk.ts index 3badfb7..f6d8da7 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -14,11 +14,11 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import jwtDecode from 'jwt-decode'; +import pkceChallenge from 'react-native-pkce-challenge' export interface SdkConfig { serverUrl: string, // your Casdoor server URL, e.g., 'https://door.casdoor.com' for the official demo site clientId: string, // the Client ID of your Casdoor application, e.g., 'b800a86702dd4d29ec4d' - clientSecret: string, // the Client Secret of your Casdoor application, e.g., '1219843a8db4695155699be3a67f10796f2ec1d5' appName: string, // the name of your Casdoor application, e.g., 'app-example' organizationName: string // the name of the Casdoor organization connected with your Casdoor application, e.g., 'casbin' redirectPath?: string // the path of the redirect URL for your Casdoor application, will be 'http://localhost:5000/callback' if not provided @@ -44,6 +44,7 @@ export interface Account { class Sdk { private config: SdkConfig + private pkce = pkceChallenge(); constructor(config: SdkConfig) { this.config = config @@ -81,7 +82,7 @@ class Sdk { const redirectUri = this.config.redirectPath && this.config.redirectPath.includes('://') ? this.config.redirectPath : `${window.location.origin}${this.config.redirectPath}`; const scope = 'read'; const state = await this.getOrSaveState(); - return `${this.config.serverUrl.trim()}/login/oauth/authorize?client_id=${this.config.clientId}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${scope}&state=${state}`; + return `${this.config.serverUrl.trim()}/login/oauth/authorize?client_id=${this.config.clientId}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${scope}&state=${state}&code_challenge=${this.pkce.codeChallenge}&code_challenge_method=S256`; } public getUserProfileUrl(userName: string, account: Account): string { @@ -119,11 +120,15 @@ class Sdk { const state = redirectUrl.substring(stateStartIndex); await AsyncStorage.setItem('casdoor-state', state); try { - const response = await fetch(`${this.config.serverUrl}/api/login/oauth/access_token?client_id=${this.config.clientId}&client_secret=${this.config.clientSecret}&grant_type=authorization_code&code=${code}`, - { - method: 'POST', - credentials: 'include', - }); + const response = await fetch(`${this.config.serverUrl.trim()}/api/login/oauth/access_token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + // @ts-ignore + body: `client_id=${this.config.clientId}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(this.config.redirectPath)}&code_verifier=${this.pkce.codeVerifier}`, + credentials: 'include', + }); if (response.ok) { const responseData = await response.json(); const token = responseData.access_token; diff --git a/test/sdk.test.ts b/test/sdk.test.ts index edec074..e95b666 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -4,7 +4,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; const sdkConfig = { serverUrl: 'https://door.casdoor.com', clientId: 'b800a86702dd4d29ec4d', - clientSecret: '1219843a8db4695155699be3a67f10796f2ec1d5', appName: 'app-example', organizationName: 'casbin', redirectPath: 'http://localhost:5000/callback', @@ -18,7 +17,6 @@ describe('sdk constructor', () => { const instanceConfig = sdk['config']; expect(instanceConfig.serverUrl).toEqual(sdkConfig.serverUrl); expect(instanceConfig.clientId).toEqual(sdkConfig.clientId); - expect(instanceConfig.clientSecret).toEqual(sdkConfig.clientSecret); expect(instanceConfig.appName).toEqual(sdkConfig.appName); expect(instanceConfig.organizationName).toEqual(sdkConfig.organizationName); expect(instanceConfig.redirectPath).toEqual(sdkConfig.redirectPath); @@ -61,7 +59,7 @@ describe('getSigninUrl', () => { it('with fixed state', async () => { const state = 'test-state'; - await AsyncStorage.setItem('casdoor-state', state); // 使用await等待异步操作完成 + await AsyncStorage.setItem('casdoor-state', state); const sdk = new Sdk(sdkConfig); const url = await sdk.getSigninUrl(); @@ -73,7 +71,7 @@ describe('getSigninUrl', () => { const sdk = new Sdk(sdkConfig); const url = await sdk.getSigninUrl(); - const state = await AsyncStorage.getItem('casdoor-state'); // 使用await等待异步操作完成 + const state = await AsyncStorage.getItem('casdoor-state'); expect(url).toContain(`state=${state}`); }); diff --git a/yarn.lock b/yarn.lock index 9cf9b08..6531cb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1774,6 +1774,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -4758,6 +4763,13 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-native-pkce-challenge@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-native-pkce-challenge/-/react-native-pkce-challenge-5.2.0.tgz#8696bba05062a463bb4a695366c11c35ad3f1202" + integrity sha512-iuOi0t8NfTzOLFkGECyVHgdUeqawKQYXdW7O4/4MncGHGYnWMCPk4EKvdPdmricIXfPq18uKb5w5RacMo25poA== + dependencies: + crypto-js "3.3.0" + read-cmd-shim@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9"