Description
Describe the bug
So, we have an Ionic Angular app for Android and iOS. We use angular-oauth2-oidc package with our server keycloak for users authentication. We noticed today that signing in doesn't work for Android version of our app (we don't know how long it does not work). We tested both platforms (code exactly the same) and iOS works fine, but android fails with token.
What is the flow?
- User clicks button for google or fb login
- App opens inApp browser by @capacitor/browser with keycloak website
- User can choose between signing in with google, facebook or just typing username and password
- After his selection keycloak redirects to app
- If token method hasValidToken() returns true then app flow is going to redirect user to other app screens
What is the problem on Android but works fine for iOS?
- Above 1, 2, 3, 4 steps works fine
- When app is back after redirection then the hasValidToken() returns always false
- User is not redirected to other app screens (screens for authenticated user)
- When user clicks again on google or fb login buttons then flow is repeated without step 3 because keycloak knows that this user is already signed in so redirection is beeing made to app
- After redirection app sometimes is minimized (not always and there are no native or js bugs visible in logcat and dev tools console)
Expected behavior
Everything should work fine as it works for iOS. It worked fine before we noticed it and none code changes has been made in this part of app for about 2 years
Desktop (please complete the following information):
- OS: Android
- Browser: Ionic App (@capacitor/browser is used for opening keycloak from oauth methods)
- Version [e.g. 22]: "angular-oauth2-oidc": "^15.0.1"
Code flow
- Plugin init is beeing made in app.module.ts like this:
const authConfig: AuthConfig = {
issuer: 'issuer url here',
requireHttps: true,
redirectUri: 'our redirect uri',
clientId: 'our client id',
responseType: 'code',
revocationEndpoint: 'revoke endpoint',
showDebugInformation: true,
useSilentRefresh: true,
openUri: (uri) => {
Browser.open({ url: uri });
},
};
const initializeAuth = (
oauthService: OAuthService,
authService: AuthService,
storageService: StorageService
): (() => Promise<void>) => async () => {
await storageService.init();
oauthService.configure(authConfig);
oauthService.setupAutomaticSilentRefresh();
authService.addAppUrlOpenListener();
return oauthService
.loadDiscoveryDocumentAndTryLogin()
.then(() => {
console.log('OAuth discovery document loaded');
})
.catch((error) => {
console.error('Error loading discovery document:', error);
});
};
...other providers in module
{ provide: APP_INITIALIZER, useFactory: initializeAuth, multi: true, deps: [OAuthService, AuthService, StorageService] },
...other providers in module
Config is fine, storageService.init() also works fine and no bugs here
- addAppUrlOpenListener method looks like this
addAppUrlOpenListener = (): void => {
App.addListener('appUrlOpen', async (event) => {
const url = new URL(event.url);
this.loggerService.sendLog('App URL opened', { url: event.url });
this.zone.run(async () => {
const queryParams: Params = {};
for (const [key, value] of url.searchParams.entries()) {
queryParams[key] = value;
}
this.router.navigate([], { relativeTo: this.activatedRoute, queryParams }).then(() => {
this.oauthService.tryLogin().then((result) => {
if (this.hasValidAccessToken) {
this.handleAfterLoginActions();
}
});
});
});
});
};
- And also we have authOnAppInit method that is fired on app init in app.component.ts to check if user is already logged in. Console logs provided here are just for debugging this problem
authOnAppInit = (): void => {
this.oauthService
.loadDiscoveryDocumentAndTryLogin({
onTokenReceived: (info) => {
console.log('Token received callback:', info);
},
onLoginError: (error) => {
console.error('Login error:', error);
},
})
.then((result) => {
this.hasValidAccessToken = this.oauthService.hasValidAccessToken();
const accessToken = this.oauthService.getAccessToken();
const accessTokenExpiration = this.oauthService.getAccessTokenExpiration();
console.log('Try login result:', result);
console.log('Access Token:', accessToken || 'No access token');
console.log('Access Token Expiration:', accessTokenExpiration ? new Date(accessTokenExpiration) : 'No expiration');
console.log('Has Valid Access Token:', this.hasValidAccessToken);
if (this.hasValidAccessToken) {
this.handleAfterLoginActions();
}
})
.catch((error) => {
console.error('Error during app initialization login check:', error);
});
this.oauthService.events.subscribe((event) => {
console.log('OAuth event received:', event,);
this.loggerService.sendLog('OAuth event', { event });
this.hasValidAccessToken = this.oauthService.hasValidAccessToken();
if (this.hasValidAccessToken) {
this.handleAfterLoginActions();
}
});
};
So, once again - for Android above console logs are:
- Try login result - true (so as far as I understand it means that login succeed)
- Access token - null
- Access token expiration - null
- Has valid access token - false
- OAuth event received - OAuthSuccessEvent {type: 'discovery_document_loaded', info: {...}}
We were debugging it for a few hours and as I wrote above, there are no errors in any of these method of plugin or even native errors from android logcat
Any ideas?