1.2.3: sign in through the new NikoAuth SSO (browser handoff)
Some checks failed
build / apk (push) Has been cancelled

The web chat moved from the in-app Google flow to NikoAuth (auth.n1ko.dev);
the old bridge no longer exists, so sign-in was impossible: the WebView
follows /api/auth/start out to the SSO host, and every non-chat host opens
in the system browser.

Sign-in now leaves for the system browser on purpose: the app intercepts
/api/auth/start and appends app=1, and the server answers the finished
callback with the /app-auth App Link, which hands the fresh session back
into the app's WebView (google blocks its own login inside WebViews, so
that round trip cannot happen in-app).
This commit is contained in:
Niko Marmeladkov 2026-10-17 16:18:39 +03:00
parent ad92e93660
commit 3ad6e6cf7c
3 changed files with 26 additions and 23 deletions

View file

@ -23,10 +23,11 @@ Download the APK from the releases page:
**https://git.n1ko.dev/Niko/nikogpt_android/releases**
Latest build:
https://git.n1ko.dev/Niko/nikogpt_android/releases/download/v1.0.0/nikogpt-android-1.0.0.apk
https://git.n1ko.dev/Niko/nikogpt_android/releases/download/v1.2.3/nikogpt-android-1.2.3.apk
Open the APK on the phone (allow installing apps from this source when Android
asks). The APK is debug-signed for now, so Android may show a warning.
asks). The APKs are signed with the project's release key; Android may still
warn because the app is installed from outside Play.
## Build
@ -34,7 +35,7 @@ Requirements: JDK 17, Android SDK (platform 35, build-tools 35.0.0).
```bash
./gradlew assembleDebug # app/build/outputs/apk/debug/app-debug.apk
./gradlew assembleRelease # unsigned; sign it yourself
./gradlew assembleRelease # release; signed with the keystore when configured
```
Or just open the project in Android Studio.
@ -58,26 +59,19 @@ EOF
```
**Installing over a debug-signed build requires uninstalling it first** (the
signatures differ). Add the release certificate's SHA-1 to the Android OAuth
client (Google Cloud) and its SHA-256 to `web.android_cert_sha256` in the bot
config for App Links.
signatures differ). The release certificate's SHA-256 must be listed in
`web.android_cert_sha256` in the bot config for App Links verification.
## Google sign-in
## Sign-in
Native "Continue with Google" is attempted in this order:
1. **Credential Manager** (modern GMS) — the standard account sheet;
2. **legacy Google Sign-In API** — this is what **microG** implements, so the
native picker works on de-Googled ROMs with microG and a Google account
added there;
3. **browser flow** — used when neither provider is available (no GMS, no
microG, or cancelled); the browser returns to the app through the
`nikogpt://auth` deep link and the session lands in the WebView.
Native sign-in needs an **Android-type OAuth client** in the same Google Cloud
project, with the app's package name (`dev.n1ko.nikogpt`) and the SHA-1 of the
signing certificate. The web client id (from the server) is passed as
`serverClientId`.
All credentials live on NikoAuth (`auth.n1ko.dev`): password, e-mail codes,
2FA and Google. The app opens the login in the system browser — Google
refuses WebView logins, and a WebView never shares the browser's cookie jar —
and comes back through a verified App Link
(`https://chat.n1ko.dev/app-auth?...`) once the login is done; the server
hands the fresh session to the app's WebView on that link. Requires the
release certificate's SHA-256 in the bot's `web.android_cert_sha256` for
App Links verification.
## Structure

View file

@ -22,8 +22,8 @@ android {
applicationId = "dev.n1ko.nikogpt"
minSdk = 29 // Android 10
targetSdk = 35
versionCode = 8
versionName = "1.2.2"
versionCode = 9
versionName = "1.2.3"
}
signingConfigs {

View file

@ -196,6 +196,15 @@ class MainActivity : AppCompatActivity() {
handleAuthUri(url)
return true
}
if (url.host == HOST && url.path == "/api/auth/start") {
// Sign-in runs in the system browser: Google refuses
// WebView logins, and a WebView never shares the
// browser's cookie jar. "app=1" makes the server answer
// with the /app-auth App Link once the login is done,
// which hands the result back into the app.
openExternally(url.buildUpon().appendQueryParameter("app", "1").build())
return true
}
if (url.host == HOST) return false
openExternally(url)
return true