Develop Android Apps using Frappe Authenticator
Prerequisites:
Add OAuth Client on frappe server
Android - using Volley for jsonrequest (ready ApplicationController, using FrappeServerCallback to get response, ERPNextContactProvider endpoints example UTSL )
Sample App: GitHub - revant/FrappeAuthExample
Check if Frappe Authenticator is installed.
if (mAccount!=null && authToken!=null) {
}
}
});
}
private void getAuthToken(String accountType, String authTokenType) {
Account[] accounts = mAccountManager.getAccountsByType(accountType);
rememberIdpSettings(accountType, authTokenType);
if (!appInstalledOrNot("io.frappe.frappeauthenticator")){
new AlertDialog.Builder(MainActivity.this)
.setTitle("Install App")
.setMessage("Please Install Frappe Authenticator")
.show();
}
else if (accounts.length == 1) {
Log.d("account", accounts[0].name);
mAccount = accounts[0];
getAuthToken(accounts[0], authTokenType);
}
private void rememberIdpSettings(String accountType, String authTokenType) {
if (idpSettings == null) {
idpSettings = new HashMap<String, String>();
}
if (!idpSettings.containsKey(accountType)) {
idpSettings.put(accountType, authTokenType);
}
}
private boolean appInstalledOrNot(String uri) {
PackageManager pm = getPackageManager();
try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) {
}
return false;
}
}
If one account found, select it, get token and use it
private void getAuthToken(String accountType, String authTokenType) {
Account[] accounts = mAccountManager.getAccountsByType(accountType);
rememberIdpSettings(accountType, authTokenType);
if (!appInstalledOrNot("io.frappe.frappeauthenticator")){
new AlertDialog.Builder(MainActivity.this)
.setTitle("Install App")
.setMessage("Please Install Frappe Authenticator")
.show();
}
else if (accounts.length == 1) {
Log.d("account", accounts[0].name);
mAccount = accounts[0];
getAuthToken(accounts[0], authTokenType);
}
else {
Intent intent = AccountManager.newChooseAccountIntent(null, null, new String[]{accountType}, null, null, null, null);
startActivityForResult(intent, 1);
}
}
if multiple accounts found, ask to select one.
new AlertDialog.Builder(MainActivity.this)
.setTitle("Install App")
.setMessage("Please Install Frappe Authenticator")
.show();
}
else if (accounts.length == 1) {
Log.d("account", accounts[0].name);
mAccount = accounts[0];
getAuthToken(accounts[0], authTokenType);
}
else {
Intent intent = AccountManager.newChooseAccountIntent(null, null, new String[]{accountType}, null, null, null, null);
startActivityForResult(intent, 1);
}
}
private void getAuthToken(final Account account, String authTokenType) {
mAccountManager.getAuthToken(account, authTokenType, null, this, new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Use token as header in API to interact with frappe REST endpoints.
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
/**
* Created by revant on 26/2/17.
*/
public class ERPNextContactProvider {
JSONArray out;
public JSONArray getContacts(String frappeServerURL, final String access_token, final FrappeServerCallback callback) {
// Post params to be sent to the server
// HashMap<String, String> params = new HashMap<String, String>();
// params.put("limit_page_length","None");
JsonObjectRequest req = new JsonObjectRequest(frappeServerURL+"/api/resource/Contact?limit_page_length=None", null,
new Response.Listener<JSONObject>() {
@Override
Frappe Authenticator will seamlessly take care of :
storing OAuth 2 bearer token - access_token / refresh_token
when getAuthToken is called it checks if access_token is valid
if access_token has expired it renews bearer_token, stores it and returns new access token
if refresh_token paired with access_token is revoked/deleted, ask user to login again.
9 Likes
https://bitbucket.org/mntechnique/androidfrappetimelog
Example repository covers
Notifications and BroadcastReceiver,
REST API GET/POST/PUT calls, using Authenticator.
Navigation Drawer has accounts listing.
5 Likes
wale
July 18, 2017, 1:16am
4
Hi @revant_one
Great job! I’ve been hoping to see projects that help with integration of the Frappe framework with native mobile apps. This looks to be a good step in the right direction
Thanks a lot
Cheers!
1 Like
wale
July 19, 2017, 10:20am
5
Hi @revant_one
Trust you’re doing great. On the issue of Mobile App Development, I’m not sure if you’re familiar with the Liferay Screens project. It’s actually a collection of fully native mobile components for rapid native app development. It’s obviously designed for the Liferay Portal but one of it’s key features is that it works with other backends too
I think it would be awesome if we could have something like this for the Frappe framework. This would cut the time and cost required to develop native mobile apps for Frappe (and ERPNext) significantly. What do you think? References:
https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/android-apps-with-liferay-screens
https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/adding-custom-interactors-to-android-screenlets
Kind regards,
1 Like
Use Case :
Mobile App logs into frappe for consuming REST API
App user is not expected to remember passwords. Password won’t be asked. Only the OTP will be verified and bearer token will be handed.
Mobile number is username
Server Side Frappe App :
Basic endpoints for non-standard otp generation and authentication.
NOTE: this is not standard TOTP mentioned here RFC 6238 - TOTP: Time-Based One-Time Password Algorithm . This was developed specifically because enter password step needed to be dropped
Standards based OTP (pyotp) generation and 2FA is being contributed and is recommended.
Dear All,
How would one go about adding Two Factor Authentication to Frappe?
Could you recommend reliable alternatives to https://www.authy.com that one could integrate with in order to have Two Factor Authentication?
Regards,
cksgb
Mobile OTP Authenticator Library for Android
This is Kotlin based library, can be imported in Java app.
XMLs designed are very basic, after importing the library override them with your own cool designs and keep the names same in your app.
It uses SmsVerifyCatcher to read the SMS. Login screen copies OTP into OTP input and proceeds automatically once SMS is received.
Everthing else is same as OAuth2Authenticator
2 Likes
@revant_one Is this enable google login in Android app if we enable frappe oauth?
tldr; No, you can only login to any other app with frappe credentials.
As a part of the flow it shows the /login page of your server.
So it will show all the social logins enabled on /login page.
but I don’t think following sequence will work.
user lands on /login > user clicks login with google > logs in to desk.
because we need following flow for bearer token
user lands on /login > user logs in with frappe account > redirect with auth code back to redirect uri.
If user first logins into frappe with google account and then sets the password for the frappe user. It will work with 2nd flow.
1 Like
Is this android mobile app available on the android store or any other similar apps? How do I access the app in order to view the app functionality which exists? Appreciate if you can direct me on how it requires to be installed?
These are source code references and libraries. Use them to develop android apps. If you’ve android studio clone the source, build the apk and try them out.
1 Like
Thanks. Is it correct to say accessing an android /ios app which connects to ErpNext does not add to the existing count of erpnext user licences?
Are there any android or ios apps on the Google or Apple store I can check out which have made use of the frappe authenticator? Alternatively, something I can install outside of the stores.
Thanks
download one of the sync .apk and test.
add oauth client on frappe server
enter the oauth client data on android app
to sync you need to go to apps > frappe authenticator on android phone and enable contacts and event permission
2 Likes
gvrvnk
September 3, 2017, 5:34pm
13
Yes. You can login to the app as any of your existing ERPNext users.
1 Like
Rick
September 24, 2017, 10:59pm
14
Some great Kotlin tutorials to learn how to make Android apps.
1 Like
Hi revant_one,
I’m sure FrappeAuthenticator#getAuthToken handles valid/invalid/refresh token and It’s attached to the Service.
Do I need to manage/call it manually? or It’s handled by AccountManager automatically?
Thanks.
intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
Log.d("frappe", TAG + "> getAuthToken");
// If the caller requested an authToken type we don't support, then
// return an error
if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) && !authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS)) {
Bundle result = new Bundle();
result = getBundle("invalid_token_type",AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS,account,response);
return result;
}
Whenever you need the valid token to make calls use am.getAuthtoken(...)
It will either
give you unexpired token
if token is expired it’ll refresh it and give unexpired token
if token is revoked it’ll show Notification and ask you to sign in again.
2 Likes
Get access token but expired and FrappeAuthenticator#getAuthToken never called
AccountManager am = AccountManager().get(context);
final AccountManagerFuture future = am.getAuthToken(account, AUTHTOKEN_TYPE_FULL_ACCESS, null, false, null,null);
FrappeAuthenticator#getAuthToken called and work as expected.
FrappeAuthenticator frappeAuthenticator = new FrappeAuthenticator(context);
Bundle future = frappeAuthenticator.getAuthToken(null,account, AUTHTOKEN_TYPE_FULL_ACCESS,null);
Am I missing something or confusing how the code work?
In manifest.xml
<service
android:name="PACKAGE_NAME.authenticator.FrappeAuthenticatorService"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
I have to manage invalid token manually as code below, otherwise FrappeAuthenticator#getAuthToken not call.
am.invalidateAuthToken(accounType,authToken);
Oh! I think you’re not using the library?
Refer this. It’s way easier!!
Easy jitpack import
1 Like
It’s a bit weird for me that your FrappeAuthenticator look easier than OAuth2Authenticator
I learn a lot from it. I’m trying to make it works, otherwise I’ll look into OAuth2Authenticator.