OneTap for Android and Micronaut
One tap is goggles newest cross platform identity service and its implementation is very neat for a user
Our aim will be to setup OneTap for Android and verify the user with a micronaut backend.
Account setup
First head here to create or configure an existing project
https://console.developers.google.com/
First, setup the oauth consent screen
keytool -keystore ~/.android/debug.keystore -list -v
(Password is android)
Now you need to do the same steps — but create a web application. Leave the defaults. You need this client ID for your app — NOT your android ID.
This will generate a client ID for you — copy this and use it for both your android application and your backend
Setting up the Android application
Add this to your app/build.gradle file:
implementation 'com.google.android.gms:play-services-auth:18.0.0
implementation "androidx.fragment:fragment:1.3.0-alpha06"
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha06"
Add the following to strings.xml placing the client ID from your create account steps you need the one from your web app — not from you android app.
<string name="one_tap_client_id"></string>
I’ve created a wrapper around the callbacks to make it more kotlin coroutines friendly:
First, sign up a user. We need to create a signup request
and we can perform the first stage of the request like this
So the next step will be requesting and then handling the result, we have included the alpha fragment library so we could use the new API for retrieving activity results
So inside our signup we want to do this:
signUpResult.launch(
IntentSenderRequest.Builder(result.pendingIntent)
.build()
)
Leaving our full implementation of signup as this
Naturally, the flow should be to sign someone in if they already have an account, so we’ll follow a similar flow to sign up
And our handler
You might have noticed the ‘error handler’ function which looks like this; and for our login if it fails it will try to sign up a user.
private fun errorHandler(onError: (() -> Unit)? = null): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, e ->
onError?.invoke()
Log.e(
TAG,
e.localizedMessage ?: "",
e
)
}
}
Altogether our class now looks like this
For the sake of this article — I’m going to take that token and manually finish the authentication when it’s printed. Change
Log.d(TAG, "Got ID token.")
to
Log.d(TAG, "Got ID token. $token")
What you want to do in production will differ — but the basic flow should be to take that token, and add it to the header “Bearer: Authorization <token>”
Implementing the backend
Create a micronaut project
mn create-application OneTapAuthenticator -l kotlin -f security-jwt
Modify the application.yml to look like this:
micronaut:
security:
enabled: true
endpoints:
login:
enabled: true
token:
jwt:
enabled: true
signatures:
secret:
generator:
secret: "${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisSecretForANewOne}"
Also, create a controller for verification
mn create-controller CreateCheckerController
Now, add this in order to verify
@Controller("/authenticated")
class CertificateCheckerController {
@Secured(SecurityRule.IS_AUTHENTICATED)
@Get("/verification")
fun verify(principal: Principal?) =
principal
}
Run the application and check that it runs, run
curl http://localhost:8080/authenticated/verification
This should fail with a 401.
Allow authentication in the app
First add the google client API to the gradle file
implementation 'com.google.api-client:google-api-client:1.30.9'
The code is fairly straightforward:
- The singleton / token validator means this will be used when a client has a Bearer token
- The token passed in is a JWT — the one that is generated in your android app (idToken)
- The rest is calling through to the Google client API to verify the certificate. We take the payload and return the authentication response
Run, and check with curl:
curl -H "Authorization: Bearer <your token from the android app>" http://localhost:8080/authenticated/verification -v
The rest is up to you — I would suggest from here that you generate a JWT from micronaut and store that (securely — in the keystore) in your application. When you launch your app, check if the JWT token exists and is valid — if not, kick off your signin flow again.
Enjoy!