Authorize your Android App with AppAuth and Identity Server 3

Every smartphone user is familiar with the following scenario:
- You install an app from your respective app store.
- Before actual using the app, you have at least to log in.
- When clicking on the login button, you are getting redirected to a web login page
- When your login has been successful, you are getting back to your app and start using it
Login via smartphone browser
But how to achieve this is as an Android developer? Do I need a broad knowledge how to setup a secure infrastructure?
When I start to introduce an authentication and authorization mechanism workflow, I definitely do not implement everything from scratch. Configuring all this stuff can be a real pain in the neck. Moreover, the probability is quite high that crucial security bugs occur.
Therefore, I reused a toolchain consisting of AppAuth for Android and Identithy Server 3 as authorization server in my last project.
AppAuth for Android
AppAuth is a powerful library communicating with OAuth 2.0 and OpenID Connect providers. All the forwarding and redirecting magic from app to browser, and vice versa, works already out of the box. That saves a lot of time and offers less working points to make any crucial security mistakes, excellent!
Embedding the dependency
1 |
compile 'net.openid:appauth:0.3.0' |
While we were configuring AppAuth last summer, the latest version was 0.3.0. In the meanwhile, it is 0.5.1.
AndroidManifest.xml Configuration
First of all, define an intent filter in the activity where you intent to perform the authorization request to the authorization server.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<application <activity android:name=".login.LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <intent-filter> <action android:name="de.novatec.android.HANDLE_AUTHORIZATION_RESPONSE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> ... </application> |
I recommend to pull the action name out to a string resource value. The name will be reused to perform authorization and access token requests in the upcoming sections.
RedirectUriReceiverActivity
Furthermore, it is required to define a redirect URI Activity. This redirect URI Activity is an invisible activity getting invoked in case of a successful (browser) login. Roughly speaking, it handles the redirection from the browser to the app and returns back the received authorization server response.
Actually, it is possible to define the AppAuth class “net.openid.appauth.RedirectUriReceiverActivity” as RedirectUriReceiverActivity. When I worked with OpenID’s hybrid flow , I had to conduct some minor changes.
An authorization server returns a redirect URI containing all relevant token parameters for the client. AppAuth always tries to resolve the given token parameters as URI query string. However, when hybrid flow has been specified as authorization code flow, Identity Server returns the tokens separated by a hash fragment spec-compliant to the OpenID standard.
1 2 3 4 5 6 7 8 |
de.novatec.android:/oauth2callback #code=someFancyCode &id_token=aCrypticIdToken &access_token=aCrypticAccessToken &token_type=Bearer&expires_in=7200 &scope=openid%20openid_a%20profile%20profile_a &state=aState &session_state=aSessionState |
Therefore, the AppAuth RedirectUriReceiverActivity has to be extended. A possible workaround could look like this:
1 2 3 4 5 6 7 |
public class HybridFlowRedirectUriReceiverActivity extends RedirectUriReceiverActivity { @Override public Intent getIntent() { return super.getIntent().setData(Uri.parse(super.getIntent().getDataString().replaceFirst("#", "?"))); } } |
After that, we can define a working RedirectUriReceiverActivity compatible with AppAuth for Android 0.3.0.
1 2 3 4 5 6 7 8 9 10 11 |
<application> ... <activity android:name=".login.HybridFlowRedirectUriReceiverActivity"> <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="de.novatec.sprit.android"/> </intent-filter> </activity> </application> |
Authorization Request
Triggering the call is not a rocket science. Of course you have to know your Identity Server’s
- Authorization endpoint, token endpoint, response types and scope.
- The client id that has been defined for your client.
- The redirect URI to resolve the browser’s authorization response to the calling app.
- The authorization action has to be set accordingly to the defined intent filter value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
private static final String AUTHORIZATION_ACTION = "de.novatec.android.HANDLE_AUTHORIZATION_RESPONSE"; ... public void performAuthorizationRequest() { String authorizationEndpoint = "https://ss-identity-server.azurewebsites.net/connect/authorize"; String tokenEndpoint = "https://ss-identity-server.azurewebsites.net/connect/token"; String clientId = "android"; String responseType = "code id_token token"; String redirectUri = "de.novatec.android:/oauth2callback"; AuthorizationService service = new AuthorizationService(context); AuthorizationRequest request = new AuthorizationRequest.Builder( new AuthorizationServiceConfiguration( authorizationEndpoint, tokenEndpoint, null), clientId, responseType, redirectUri)) .setScopes("openid", "openid_a", "profile, "profile_a") .build(); service.performAuthorizationRequest(request, PendingIntent.getActivity(context, request.hashCode(), new Intent(AUTHORIZATION_ACTION), 0)); service.dispose(); } |
Token Request
Briefly worded, in OpenID Connect the authorization request is the first step to receive an authorization code via a “user-agent”. In our case, the user-agent is the “browser” sending our login data to the authorization server.
The app is now capable of exchanging an authorization code for an access token from the Identity Server. Via this access token, the client is allowed to access the services of your backend infrastructure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private static final String AUTHORIZATION_ACTION = "de.novatec.android.HANDLE_AUTHORIZATION_RESPONSE"; private static final String SECRET = "secret"; @Pref Preferences_ preferences; ... @Override public void onCreate(Bundle b) { Intent intent = getIntent(); AuthorizationResponse response = AuthorizationResponse.fromIntent(intent); AuthorizationException exception = AuthorizationException.fromIntent(intent); if (response != null && AUTHORIZATION_ACTION.equals(intent.getAction())) { AuthorizationService service = new AuthorizationService(this); service.performTokenRequest(request, new ClientSecretBasic(SECRET), (tokenResponse, exception) -> { authState.update(response.createTokenExchangeRequest(), exception); preferences.getAuthState().set(authState.jsonSerializeString()); service.dispose(); }); } } |
The same mechanism can also be used to refresh an access token.
Generally, shared preferences should be used to persist and read the access token. Thereby, it can easily be reused in your HTTP authorization header.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import okhttp3.Interceptor; public class AuthorizationInterceptor implements Interceptor { @Pref Preferences_ preferences; @Override public Response intercept(Chain chain) throws Exception { String token = AuthState.jsonDeserialize(preferences.getAuthState()); if (Strings.isNullOrEmpty(token)) { return chain.proceed(chain.request()); } else { return chain.proceed(chain .request() .newBuilder() .addHeader(HttpHeaders.AUTHORIZATION, AuthorizationResponse.TOKEN_TYPE_BEARER + ' ' + token) .build()); } } } |
That is the way to show your services your admission ticket.
Btw: I used Android Annotations to access my shared preferences.
Identity Server 3
Nevertheless, the perfect Android client setup is useless if your authorization server does not speak the same language as the app does.
It is quite straightforward to configure an Android client definition for Identity Server 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
namespace IdentityServer { public class Clients { public static IEnumerable<Client> Get() { return new[] { new Client { Enabled = true, ClientName = "Android Client", ClientId = "android", Flow = Flows.HybridWithProofKey, AccessTokenLifetime = 60*60*2, IdentityTokenLifetime = 60*60*2, RefreshTokenExpiration = TokenExpiration.Sliding, RefreshTokenUsage = TokenUsage.OneTimeOnly, SlidingRefreshTokenLifetime = 60*60*24, RedirectUris = new List<string> { "de.novatec.android:/oauth2callback" }, PostLogoutRedirectUris = new List<String> { "de.novatec.android:/oauth2callback" }, ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) }, RequireConsent = false, AllowRememberConsent = true, AllowAccessToAllScopes = true } }; } } } |
Did you recognise the client id, secret and redirect URIs? There are the same as defined in our Android client.
Despite of some weak points, a common way to provide a proper authentication and authorization mechanism for mobile clients is the proven hybrid with proof key flow of OpenID Connect.
Conclusion
Finally, this was a short walkthrough how to configure your Android app with AppAuth and Identity Server 3 as authorization server.
Check out the attached references for further background information!
Cheers,
Stefan
References
AppAuth for Android
- https://codelabs.developers.google.com/codelabs/appauth-android-codelab/#0
- https://github.com/openid/AppAuth-Android
OpenID Connect
- http://connect2id.com/learn/openid-connect
- http://openid.net/connect/
- https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse
- https://leastprivilege.com/2013/11/13/authorization-servers-are-good-for-you-and-your-web-apis/
- http://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-choosing-the-right-flows/
Identity Server
Comment article
Recent posts






Comments
Dominick Baier
No-one is the bad guy. AppAuth assumes code flow – not hybrid. That’s all.
Stefan Nägele
Hello Dominick,
thx for the clarification! Great to know that AppAuth is the actual bad guy.
The post has been updated.
Kind regards,
Stefan
Dominick Baier
“Due to the fact that Identity Server 3 does not agree with the URL standard using “?” for query parameters, the RedirectUriReceiverActivity has to be customized. Identity Server 3 is using “#” as separator instead.”
That sentence is wrong – IdentityServer behaves spec-compliant (and is also official certified by the OpenID Foundation). Tokens must be sent after a hash fragment in hybrid flow.
https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse
Please update the post.