I'm building a React Native app and I'm trying to connect it with the Keycloak instance I'm already using.
I could successfully setup logging in and out to the app with expo-keycloak (my app's managed by Expo). This works fine for the most part, but I would like to have a native login form (so one that's built with RN's UI components and lives on it's own screen) instead of opening a webview with Keycloak's HTML-based form.
All the libraries and tutorials I've found so far use a webview and deep linking for a callback URL. Is there a library that I can integrate with my custom-made in-app forms or do I have to write this from scratch using KC's REST API? I was looking into offline tokens, but I don't get how to get an access token automatically with the offline token before making authenticated requests (almost all of the API endpoints I would have to call inside the app require an Authentication header with the access token added as the Bearer token).
I would also like to keep the user logged in "forever", as in until they don't explicitly log out - as it's common in mobile apps. My current KC setup shows the Login screen again after restarting the app, and when pressing the login button, it starts the webview again but without having to fill in the form again (so I'm automatically redirected to the app after a short blink of the webview). Why is that?
To be clear I'm not looking for a complete solution, it's enough if someone just points me in the right direction or explains how to do this conceptually.
I've done this in a react app without any libraries.
You need 3 endpoints that are already exposed by Keycloak and a simple token refresh mechanism.
I'm providing CURL requests to simplify things. In the react client, make sure to use URLSearchParams
for the body of the request (this took me a day to debug cors errors). For more info I suggest this tutorial or the official documentation Admin REST API
1. Login / request token
curl --location --request POST 'http://keycloakurl:keycloakport/realms/yourrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=yourclient' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=youruser' \
--data-urlencode 'password=yourpass' \
--data-urlencode 'client_secret=yoursecret'
2. Refresh token
This is obtained from the login step. You should store this token in a store or in local browser storage. This should be refreshed according to your keycloak config (every 5 min by default)
curl --location --request POST 'http://keycloakurl:keycloakport/realms/yourrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=yourclient' \
--data-urlencode 'client_secret=yoursecret' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=eyJhbGciOiJI.....
3. Logout
curl --location --request POST 'http://keycloakurl:keycloakport/realms/yourrealm/protocol/openid-connect/logout' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'refresh_token=eyJhbGciOiJIU.....' \
--data-urlencode 'client_id=yourclient' \
--data-urlencode 'client_secret=yoursecret'
In my client I'm using React + Redux + RxJS so I'm updating my token like this :
const token = useSelector((state: AppState) => state.auth?.token);
const tokenReceivedTime = useSelector((state: AppState) => state.auth?.tokenReceivedTime);
// Refresh token
useEffect(() => {
if (!token || !token.expires_in || !tokenReceivedTime) return;
const tokenExpiryTime = tokenReceivedTime / 1000 + token.expires_in;
const checkTokenExpiry = () => {
const currentTime = Date.now() / 1000;
if (currentTime >= tokenExpiryTime) {
console.info("Token expired, refreshing...");
dispatch(refreshToken());
}
}
const interval = setInterval(checkTokenExpiry, 60000);
return () => clearInterval(interval);
}, [token, tokenReceivedTime, dispatch]);