- Clone sdk
git clone --depth=1 --branch=master https://github.com/megicalcompany/MegicalEasyAccess-SDK-Android
- Add module to project
settings.gradle:
include ':MegicalEasyAccess-SDK-Android'
app/build.gradle:
dependencies {
implementation project(":MegicalEasyAccess-SDK-Android")
}
-
clone this repository and open it in android studio
-
clone sdk
git clone --depth=1 --branch=master https://github.com/megicalcompany/MegicalEasyAccess-SDK-Android
- build app
./gradlew assemble
Test app registration token can be obtained from:
https://playground.hightrust.id/demo/
You must login using working id card.
Web-page contains app-link which you can use to open example app and automatically register client.
Registration token can be used only once. It is used to fetch openId client data from test-service.
Resource returns auth-service url, client data and one time client registration token for auth-service.
val (
clientToken,
authEnvUrl,
authEnv,
redirectUrls,
appId,
audience
) = playgroundRestApi.openIdClientData(testAppToken)
Use client token to register oauth client with auth-service.
authEnvUrlauth-service url.clientTokenreturned from test-service. One use only. 5 minutes ttldeviceIdstring. Example app uses random uuid. https://developer.android.com/training/articles/user-data-ids#kotlinkeyIdname of keypair in keychain. Example app uses appId.
Save client data for later use. Keypair is stored in trust zone. Other data can be public.
Registering client should be only once when user first time starts to use app.
megicalAuthApi.registerClient(
authEnvUrl,
clientToken,
deviceId,
keyId,
callback = object : MegicalCallback<Client> {
override fun onSuccess(response: Client) {
// save client data for later use
}
override fun onFailure(error: MegicalException) {
// handle errors
}
}
)
After client is registered you can initiate authentication with auth-service.
authEnvMegical Easy Access app uses this to determine which env to connect.authEnvUrlauth-service url.keyIdkeychain name.clientIdoauth client idaudienceaccessToken audience. introspect returns this audience inforedirectUrlApp callback url. Should be unique per app.
megicalAuthApi.initAuthentication(
authEnv,
authEnvUrl,
keyId,
clientId,
audience,
redirectUrl,
object : MegicalCallback<LoginData> {
override fun onSuccess(response: LoginData) {
// save login data
}
override fun onFailure(error: MegicalException) {
handle errors
}
}
)
loginData.appLink can be used to open Megical Easy Access if it is installed on device.
If it is not installed, app should show loginCode and/or appLink qr-code.
private fun handleLoginData(loginData: LoginData) {
val intent = Intent(Intent.ACTION_VIEW, loginData.appLink)
if (intent.resolveActivity(requireActivity().packageManager) != null) {
startActivityForResult(intent, LOGIN_ACTIVITY)
} else {
viewModel.fetchMetadata()
viewModel.fetchLoginState()
loginCodeMessage.text = loginData.loginCode
try {
val qrgEncoder =
QRGEncoder(loginData.appLink.toString(), null, QRGContents.Type.TEXT, 200)
qrCode.setImageBitmap(qrgEncoder.bitmap)
} catch (e: WriterException) {
Timber.e(e)
}
}
}
onActivityResult must be implemented to handle return from Megical Easy Access app.
It should call verifyAuthentication if activity was successful.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == LOGIN_ACTIVITY) {
viewModel.verifyLoginData()
} else {
Timber.e("Invalid requestCode: $requestCode")
}
}
fun verifyLoginData() {
megicalAuthApi.verifyAuthentication(
object : MegicalCallback<TokenSet> {
override fun onSuccess(response: TokenSet) {
// Returns tokenset with accessToken and idToken
}
override fun onFailure(error: MegicalException) {
// handle errors
}
}
)
}
If Megical Easy Access app is not installed in device, the app has to poll auth-service for login state.
5 seconds should be good polling interval.
When login state is Updated call megicalAuthApi.verifyAuthentication().
private fun handleLoginState(loginState: LoginState) {
when (loginState) {
LoginState.Init,
LoginState.Started,
-> {
Thread {
Handler(Looper.getMainLooper())
.postDelayed(
viewModel::fetchLoginState,
5000
)
}.start()
}
LoginState.Updated -> {
viewModel.verifyLoginData()
}
else -> {
Timber.e("Unhandled state")
}
}
}
Call deleteClient to destroy client from auth-service and to remove client key pair from keychain. Delete also saved client data.
This is optional. There shouldn't be real need to delete client from auth-service.
megicalAuthApi.deleteClient(
authEnvUrl,
clientId,
keyId,
object : MegicalCallback<Unit> {
override fun onSuccess(response: Unit) {
// Client was deleted
}
override fun onFailure(error: MegicalException) {
// Handle error
}
})