如何在React Native中获取Keycloak认证Token?
如何用Keycloak授权码换取可用于REST API的用户Token(React Native Expo场景)
问题背景
我首次使用Keycloak,正在为采用Keycloak做用户认证的网站开发React Native(Expo)应用。目前已完成登录并获取到如下成功响应:
{"authentication": null, "error": null, "errorCode": null, "params": {"code": "32c1d719-c8ce-4c1b-8800-5a853095da71.3fe7ff3e-feb2-47c9-b101-b24c90d596b4.918d5daa-5b42-4480-8832-3c88c5e876ad", "session_state": "3fe7ff3e-feb2-47c9-b101-b24c90d596b4", "state": "sVF3SdDAYg"}, "type": "success", "url": "exp://10.11.1.10:8081?state=sVF3SdDAYg&session_state=3fe7ff3e-feb2-47c9-b101-b24c90d596b4&code=32c1d719-c8ce-4c1b-8800-5a853095da71.3fe7ff3e-feb2-47c9-b101-b24c90d596b4.918d5daa-5b42-4480-8832-3c88c5e876ad"}
但不知如何获取可用于REST API的用户Token。我的登录代码如下:
import React, { useEffect, useState } from 'react'; import * as WebBrowser from 'expo-web-browser'; import { useAuthRequest, useAutoDiscovery } from 'expo-auth-session'; import { Button, Text, View } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function LoginPage() { const discovery = useAutoDiscovery('https://@@@@@@@@@@@@@@@@@@@@@@@@@'); const redirectUri = 'exp://10.11.1.10:8081'; const [accessToken, setAccessToken] = useState(); const [request, result, promptAsync] = useAuthRequest( { clientId: 'mywebsite', redirectUri: redirectUri, scopes: ['openid', 'profile'], usePKCE: true, }, discovery,); console.log(result) return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Login!" disabled={!request} onPress={() => promptAsync()} /> {result && <Text>{JSON.stringify(result, null, 2)}</Text>} </View> ); }
我尝试使用Token REST API端点并传入密钥,但收到“no token”错误。
解决方案
你已经拿到了授权码(code),接下来需要用这个code去Keycloak的token端点换取access token、id token等凭证,以下是具体步骤:
1. 先确认Keycloak客户端配置
- 确保客户端
mywebsite开启了Authorization Code Flow(你用了PKCE,这个流程必须启用) Valid Redirect URIs必须包含你的exp://10.11.1.10:8081- React Native属于公开客户端,需关闭
Client Authentication选项(不需要客户端密钥)
2. 用授权码换取Token的代码实现
在result.type === 'success'的时机,调用Keycloak的token端点,注意必须传入PKCE所需的code_verifier:
import React, { useEffect, useState } from 'react'; import * as WebBrowser from 'expo-web-browser'; import { useAuthRequest, useAutoDiscovery } from 'expo-auth-session'; import { Button, Text, View } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function LoginPage() { // 替换为你的Keycloak realm地址 const discovery = useAutoDiscovery('https://你的Keycloak域名/realms/你的realm名称'); const redirectUri = 'exp://10.11.1.10:8081'; const [accessToken, setAccessToken] = useState(null); const [request, result, promptAsync] = useAuthRequest( { clientId: 'mywebsite', redirectUri: redirectUri, scopes: ['openid', 'profile', 'email'], // 按需添加需要的权限 scope usePKCE: true, }, discovery, ); // 拿到授权码后请求Token useEffect(() => { if (result?.type === 'success') { const { code } = result.params; fetch(`${discovery.tokenEndpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: 'mywebsite', grant_type: 'authorization_code', code: code, redirect_uri: redirectUri, code_verifier: request.codeVerifier, // PKCE流程必需参数 }), }) .then(res => res.json()) .then(data => { if (data.access_token) { setAccessToken(data.access_token); console.log('Access Token:', data.access_token); console.log('ID Token:', data.id_token); console.log('Refresh Token:', data.refresh_token); } else { console.error('获取Token失败:', data); } }) .catch(err => console.error('请求错误:', err)); } }, [result, request, discovery, redirectUri]); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}> <Button title="Login!" disabled={!request} onPress={() => promptAsync()} /> {result && <Text style={{ marginTop: 20 }}>{JSON.stringify(result, null, 2)}</Text>} {accessToken && <Text style={{ marginTop: 20, color: 'green' }}>已获取到Access Token</Text>} </View> ); }
3. 常见错误排查
- "no token"错误:你可能给公开客户端传入了
client_secret,或者缺少code_verifier参数。公开客户端不需要密钥,且PKCE流程必须携带code_verifier。 - 检查
discovery是否正确加载:可以console.log(discovery)确认tokenEndpoint是否存在且地址正确。 - 授权码有效期很短(Keycloak默认1分钟),必须在拿到code后立即发起换token请求。
4. Token的使用方式
拿到access_token后,在REST API请求的Authorization头中携带即可:
fetch('你的API接口地址', { headers: { 'Authorization': `Bearer ${accessToken}` } })
内容的提问来源于stack exchange,提问作者Tomas
相关产品推荐
相关产品推荐

