-
-
Save lemmensaxel/72ece5cd00026cc05888701d7d65fbe0 to your computer and use it in GitHub Desktop.
import { | |
ActivityIndicator, | |
Button, | |
ScrollView, | |
Text, | |
View, | |
} from "react-native"; | |
import * as AuthSession from "expo-auth-session"; | |
import * as WebBrowser from "expo-web-browser"; | |
import { useEffect, useState } from "react"; | |
WebBrowser.maybeCompleteAuthSession(); | |
const redirectUri = AuthSession.makeRedirectUri({ | |
useProxy: true, | |
}); | |
// Keycloak details | |
const keycloakUri = ""; | |
const keycloakRealm = ""; | |
const clientId = ""; | |
export function generateShortUUID() { | |
return Math.random().toString(36).substring(2, 15); | |
} | |
export default function App() { | |
const [accessToken, setAccessToken] = useState<string>(); | |
const [idToken, setIdToken] = useState<string>(); | |
const [refreshToken, setRefreshToken] = useState<string>(); | |
const [discoveryResult, setDiscoveryResult] = | |
useState<AuthSession.DiscoveryDocument>(); | |
// Fetch OIDC discovery document once | |
useEffect(() => { | |
const getDiscoveryDocument = async () => { | |
const discoveryDocument = await AuthSession.fetchDiscoveryAsync( | |
`${keycloakUri}/realms/${keycloakRealm}` | |
); | |
setDiscoveryResult(discoveryDocument); | |
}; | |
getDiscoveryDocument(); | |
}, []); | |
const login = async () => { | |
const state = generateShortUUID(); | |
// Get Authorization code | |
const authRequestOptions: AuthSession.AuthRequestConfig = { | |
responseType: AuthSession.ResponseType.Code, | |
clientId, | |
redirectUri: redirectUri, | |
prompt: AuthSession.Prompt.Login, | |
scopes: ["openid", "profile", "email", "offline_access"], | |
state: state, | |
usePKCE: true, | |
}; | |
const authRequest = new AuthSession.AuthRequest(authRequestOptions); | |
const authorizeResult = await authRequest.promptAsync(discoveryResult!, { | |
useProxy: true, | |
}); | |
if (authorizeResult.type === "success") { | |
// If successful, get tokens | |
const tokenResult = await AuthSession.exchangeCodeAsync( | |
{ | |
code: authorizeResult.params.code, | |
clientId: clientId, | |
redirectUri: redirectUri, | |
extraParams: { | |
code_verifier: authRequest.codeVerifier || "", | |
}, | |
}, | |
discoveryResult! | |
); | |
setAccessToken(tokenResult.accessToken); | |
setIdToken(tokenResult.idToken); | |
setRefreshToken(tokenResult.refreshToken); | |
} | |
}; | |
const refresh = async () => { | |
const refreshTokenObject: AuthSession.RefreshTokenRequestConfig = { | |
clientId: clientId, | |
refreshToken: refreshToken, | |
}; | |
const tokenResult = await AuthSession.refreshAsync( | |
refreshTokenObject, | |
discoveryResult! | |
); | |
setAccessToken(tokenResult.accessToken); | |
setIdToken(tokenResult.idToken); | |
setRefreshToken(tokenResult.refreshToken); | |
}; | |
const logout = async () => { | |
if (!accessToken) return; | |
const redirectUrl = AuthSession.makeRedirectUri({ useProxy: false }); | |
const revoked = await AuthSession.revokeAsync( | |
{ token: accessToken }, | |
discoveryResult! | |
); | |
if (!revoked) return; | |
// The default revokeAsync method doesn't work for Keycloak, we need to explicitely invoke the OIDC endSessionEndpoint with the correct parameters | |
const logoutUrl = `${discoveryResult! | |
.endSessionEndpoint!}?client_id=${clientId}&post_logout_redirect_uri=${redirectUrl}&id_token_hint=${idToken}`; | |
const res = await WebBrowser.openAuthSessionAsync(logoutUrl, redirectUrl); | |
if (res.type === "success") { | |
setAccessToken(undefined); | |
setIdToken(undefined); | |
setRefreshToken(undefined); | |
} | |
}; | |
if (!discoveryResult) return <ActivityIndicator />; | |
return ( | |
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> | |
{refreshToken ? ( | |
<View | |
style={{ flex: 1, justifyContent: "center", alignItems: "center" }} | |
> | |
<View> | |
<ScrollView style={{ flex: 1 }}> | |
<Text>AccessToken: {accessToken}</Text> | |
<Text>idToken: {idToken}</Text> | |
<Text>refreshToken: {refreshToken}</Text> | |
</ScrollView> | |
</View> | |
<View> | |
<Button title="Refresh" onPress={refresh} /> | |
<Button title="Logout" onPress={logout} /> | |
</View> | |
</View> | |
) : ( | |
<Button title="Login" onPress={login} /> | |
)} | |
</View> | |
); | |
} |
Hello, thanks for the example! I am using and it is working. This is my first contact with Expo AuthSession. But there is a problem: everytime I try to login again Keycloak remebers my e-mail, but asks for the password again. This does not happens with Postman, or other web front-ends... I think this is related to the following section in Expo AuthSession documentation:
"Note: the web browser should share cookies with your system web browser so that users do not need to sign in again if they are already authenticated on the system browser -- Expo's WebBrowser API takes care of this."
If the API takes care of this, something is wrong and I did not found a way to tune... Anyone experiencing this? Maybe this is related to a development build? Maybe Chrome in development build is restricting cookies?
Thanks!
Answering my own question : prompt: AuthSession.Prompt.Login
at line 51, according to OpenId documentation here it will make the server prompt for reauthentication. It says: "The Authorization Server SHOULD prompt the End-User for reauthentication". Keycloak is doing exactly that. To solve my case, I just had to not send the prompt parameter. Now it works as I expect: it will only promt for login again in case of complete timeout without refresh. 😄 😃 💯
Thank you for your example, super cool as the Expo documentation is lacking important sections of the process.