TIP: Activate via URL and Send Arguments
The most secure password in the world is the one that doesn’t exist. You remove the user from the equation with a completely random key. To be fair this has some drawbacks and a password still exists somewhere (in your phone/email) but generally this works rather well…
The trick is simple, if we want to authenticate a user we can email him a single use URL e.g. mycoolapp://act-32548b09-d328-4330-8243-d7d30c322e40
. As you can see that’s pretty hard to guess or brute force. Once clicked the URL becomes invalid so even if it’s exposed somehow it would still be irrelevant. To do this we need two parts:
- The server logic
- Client URL handling
Both are pretty easy.
The Server
One caveat is that the mycoolapp
will work on the device but you can’t click it in an email or in a browser. So we will need an https
URL from your server.
The server would look something like this, notice that this is Spring Boot Controller code but you should be able to use any server out there:
public boolean sendSigninEmail(String e) { List<UserObj> ul = users.findByEmailIgnoreCase(e); if(ul.isEmpty()) { return false; } UserObj u = ul.get(0); u.setHashedActivationToken(UUID.randomUUID().toString()); (1) users.save(u); (2) email.sendEmail(e, "Signin to the Codename One App", "This is a one time link to activate the Codename One App. Click this link on your mobile device: \n\nhttps://ourserverurl.com/app/activateURL?token=act-" + u.getHashedActivationToken()); (3) return true; } public User activateViaToken(String t) throws ServerAppAPIException { List<UserObj> ul = users.findByHashedActivationToken(t); (4) if(ul.isEmpty()) { throw new ServerAppAPIException(ServerErrorCodes.NOT_FOUND); } UserObj u = ul.get(0); String val = u.getAppToken(); (5) u.setHashedActivationToken(null); (6) users.save(u); User r = u.getUser(); r.setAppToken(u.getAppToken()); return r; }
1 | We use UUID to generate the long activation string |
2 | We save it in the database overwriting an older URL if it exists |
3 | We can send an email or SMS with the HTTPS URL to activate the app |
4 | Next we activate the user account with the received token. We find the right account entry |
5 | An access token is a secure password generated by the server that’s completely random and only visible to the app |
6 | The activation token used in the URL is removed now making the URL a single use tool |
All of that is mostly simple but there is still one missing piece. Our app will expect a mycoolapp
URL and an HTTPS URL won’t launch it. The solution is a 302 redirect:
@RequestMapping(value="/activateURL", method=RequestMethod.GET) public void activateURL(@RequestParam String token, HttpServletResponse httpServletResponse) { httpServletResponse.setHeader("Location", "mycoolapp://" + token); httpServletResponse.setStatus(302); }
This sends the device to the mycoolapp
URL automatically and launches your app with the token!
Client Side
On the client we need to intercept the mycoolapp
URL and parse it. First we need to add two new build hints:
android.xintent_filter=<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="mycoolapp" /> </intent-filter> ios.plistInject=<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLName</key> <string>com.mycompany.myapp.package.name</string> </dict> <dict> <key>CFBundleURLSchemes</key> <array> <string>mycoolapp</string> </array> </dict> </array>
Don’t forget to fix
mycoolapp
andcom.mycompany.myapp.package.name
to the appropriate values in your app
Next all we need to do is detect the URL in the start()
method. This needs to reside before the code that checks the current Form
:
String arg = getProperty("AppArg", null); (1) if(arg != null) { if(arg.contains("//")) { (2) List<String> strs = StringUtil.tokenize(arg, "/"); arg = strs.get(strs.size() - 1); while(arg.startsWith("/")) { arg = arg.substring(1); } } if(!arg.startsWith("act-")) { (3) showLoginForm(); callSerially(() -> Dialog.show("Invalid Key", "The Activation URL is invalid", "OK", null)); return; } arg = arg.substring(4); Form activating = new Form("Activating", new BorderLayout(BorderLayout.CENTER_BEHAVIOR_CENTER)); activating.add(CENTER, new InfiniteProgress()); activating.show(); sendActivationTokenToServer(arg); (4) return; }
1 | This is from the CN class globally imported. The app argument is the URL |
2 | We remove the URL portion of the argument |
3 | The act- prefix is there to validate the URL is correct |
4 | This sends the activation key to the server logic we discussed above |
Testing in The Simulator
This will work in iOS and Android. Starting next week you could also test this on the simulator using the new Send App Argument menu option in the simulator.
To integrate this properly into an app you would normally have a login menu that accepts only the email/phone. Or a system in your web based UI to send an invite link to the app.
Whatsapp uses an inverse of this trick to activate their desktop app. They show a QR code to your device and once you scan that QR code with your whatsapp phone install the desktop version is activated. That’s much better than passwords.
Published on Java Code Geeks with permission by Shai Almog, partner at our JCG program. See the original article here: TIP: Activate via URL and Send Arguments
Opinions expressed by Java Code Geeks contributors are their own. |