Skip to content

fix(#401): update OIDC handling to use custom tabs #405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 23, 2025
Merged

Conversation

jkuester
Copy link
Contributor

@jkuester jkuester commented Jul 11, 2025

Closes #401

The PR updates the OIDC handling added in #397 to open the OIDC authentication links in an Android Custom Tab instead of keeping the OIDC authentication flow contained in the root webview.

This approach requires that the app be running with a verified app link (can either be manually verified or configured for auto-verification).

Unlike behavior previously observed in child webviews, the Custom Tabs seem to resolve these app links even when they come as a re-direct at the end of the OIDC authentication flow. Using a Custom Tab (from the default browser) to execute the login flow not only allows for Google logins, but also should allow for true "single" sign-on in the sense that if the user already has a session in their browser with the OIDC provider, they will not have to re-authenticate when logging into the CHT.

As noted out on the issue thread, the existing app link implementation via AppUrlIntentActivity was causing issues with a weird race condition where the url with the OIDC auth_code was not evaluated in the cht-android webview fast enough, but instead somehow it was getting evaluated in the default browser first. This meant that by the time the cht-android webview got around to submitting the auth_code it was invalid (since it had already been used...). To fix this, I have simplified the app link handling to just launch directly into the EmbeddedBrowserActivity instead of launching the AppUrlIntentActivity which then launched the EmbeddedBrowserActivity. Overall this seems like an acceptable refactor because it reduces some complexity/redundancy without really sacrificing anything.


Tangential to the original issue, but my changes here also apparently fix deep linking via app links. It seems that at some point the ability to deep link (e.g. to a specific person/place/etc) was broken by updates to our "lastUrl" logic. That should be fixed now.

@jkuester jkuester changed the title fix: update OIDC handling to use custom tabs fix(#401): update OIDC handling to use custom tabs Jul 11, 2025
tools:ignore="DiscouragedApi"/>
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One subtle change here is switching the launchMode from singleInstance to singleTask. As far as I could tell, we always had singleInstace set for the AppUrlIntentActivity, but just reading the documentation, it feels like that is not really the right choice. With the way I currently understand things, singleTask will allow us to always re-use the existing instance of EmbeddedBrowserActivity and continue the task chain as normal. The singleInstance docs note that any additional activities launched off of the root are not part of the same task chain.

TLDR, as far as I can tell, singleTask is "more normal" than singleInstance and we get the desired behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this was a good read. I remember we had one version of the App that ended up launching multiple instances. Of course the background instances could not be reached or removed, and the only way they disappeared was when the whole app was killed.
:(

singleTask sounds like would not create a new activity for our use case, but we should be careful none the less.

}
}

@Override
protected void onNewIntent (Intent intent) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR, this is now the re-entry point during the OIDC flow. The instance of EmbeddedBrowserActivity is created when displaying the initial login page. Then we pop to the Custom Tab to do the OIDC auth. Finally, the redirect_uri brings us back here via the validated app url.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, I have not found any convenient way of testing the code in the onCreate method yet. Given that we don't have any existing tests, I do not think we should block on this, though... 😓

@jkuester jkuester requested a review from lorerod July 12, 2025 01:01
@jkuester jkuester removed the request for review from lorerod July 12, 2025 01:26
@jkuester
Copy link
Contributor Author

@dianabarsan could you have a look at this when you get a chance? 🙏 For convenient manual testing, I have configured gamma for SSO login (via Google OIDC). I provisioned a CHT user associated with your medic.org email that you should be able to use. 👍

@icrc-fdeniger can you test this out from your end too? I am hoping this fix resolves the issues with Microsoft Entra and using Google accounts for guest logins.

@jkuester jkuester requested a review from dianabarsan July 12, 2025 01:34
Copy link
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomTabsIntent sounds like a good choice! How do they behave in reality? OH OH just watched a video. I believe apps like FB and Insta use custom tabs to open adds.

Cool stuff, I asked some questions for clarity.

tools:ignore="DiscouragedApi"/>
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this was a good read. I remember we had one version of the App that ended up launching multiple instances. Of course the background instances could not be reached or removed, and the only way they disappeared was when the whole app was killed.
:(

singleTask sounds like would not create a new activity for our use case, but we should be careful none the less.

<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/scheme" android:host="@string/app_host" android:pathPattern=".*"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this section was added for token login. Does this still open the existent activity when an intent is triggered from SMS?

Copy link
Contributor Author

@jkuester jkuester Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this section was added for token login.

Correct. This is how token login links (and really any other kind of deep-linking) works.

Does this still open the existent activity when an intent is triggered from SMS?

Yes, this is key and is the whole point of the launchMode="singleTask" configuration. With that launchMode, (and with the previous singleInstance), if we have an existing task instance of EmbeddedBrowserActivity, then incoming intents will be directed into that task (instead of creating a new one). If we did not set the launchMode, then by default Android I think Android would launch a separate instance of cht-android each time a deep-link is tapped.

@Override
protected void onNewIntent(Intent intent) {
Uri appLinkData = intent.getData();
browseTo(appLinkData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this what makes token login not launch a new instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, it is the launchMode configuration that "makes" a new instance not get launched. But, yes, this is the code that receives the incoming intent for an existing instance of EmbeddedBrowserActivity when a token login link is tapped.

One thing to note is that based on my testing, there is a bit of a difference between the code flows for launching an app link when no cht-android instance is currently running, vs when you already have an instance running in the background or something. In the first case, the onCreate method above is called and the app-url intent is handled by the code at line 128. onNewIntent is not called when onCreate is called. In the second case (when an instance of cht-android is already running in the background), the incoming app-url intent triggers the onNewIntent method, but the onCreate is not called (since we are not creating a new instance of EmbeddedBrowserActivity. Since both methods ultimately end up called browseTo, the functionality/UX of what happens remains the same either way.

@icrc-fdeniger
Copy link
Contributor

@dianabarsan could you have a look at this when you get a chance? 🙏 For convenient manual testing, I have configured gamma for SSO login (via Google OIDC). I provisioned a CHT user associated with your medic.org email that you should be able to use. 👍

@icrc-fdeniger can you test this out from your end too? I am hoping this fix resolves the issues with Microsoft Entra and using Google accounts for guest logins.

@jkuester I confirm it's working well with our setup. Thanks a lot !
FYI: for guest logins, the issue was due to the fact that the HTTP request for the file service-worker.js doesn't send the expected header user-agent. We had to create an exception for this file.

@jkuester
Copy link
Contributor Author

I confirm it's working well with our setup.
@icrc-fdeniger that is great to hear! 🎉

FYI: for guest logins, the issue was due to the fact that the HTTP request for the file service-worker.js doesn't send the expected header user-agent. We had to create an exception for this file.

Can you describe more about this? (Or is there a separate ticket filed somewhere for it?) Is this a cht-core issue we are looking at or is it something else related to the SSO flow?

@icrc-fdeniger
Copy link
Contributor

@jkuester don't think it's an issue on cht. It's related to ICRC's way to accept request from the mobile App.

@jkuester jkuester merged commit acbaaca into master Jul 23, 2025
8 checks passed
@jkuester jkuester deleted the sso-custom-tab branch July 23, 2025 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SSO Login with Google is Unsupported
3 participants