asgardeo
2024/07/08
 
8 Jul, 2024 | 3 min read

Providing a Secure In-App Login Experience with Authentication API

  • Janak Amarasena
  • Technical Lead - WSO2

Application developers want to provide the most secure and seamless login experience for their users, but even when following OAuth and OpenID Connect (OIDC) best practices, user experience issues can still be a problem. In this article, we will walk through how developers can provide a secure and seamless login experience to users by providing the login functionality natively within the app itself. We will briefly touch upon and refresh on the best practice to incorporate login to an app with OAuth and OIDC, discuss the user experience issues that arise when incorporating the best practices, and then walk through in detail how Asgardeo’s authentication API provides the capability to securely incorporate login, natively within an application while providing a seamless experience to the users.

While all details we discuss in this article are relevant to WSO2's identity and access management product lineup which includes WSO2 Identity Server (version 7.0.0 onwards), Asgardeo, and Private Identity Cloud, this article will only use Asgardeo when referring to capabilities.

When incorporating login into an application the de facto standard is using OAuth combined with OpenID Connect. OAuth is the most widely used access delegation protocol which is used to authorize apps on behalf of users to perform actions on user-owned resources. OpenID Connect (OIDC) is the identity layer on top of OAuth that allows for user authentication. This article will be focusing on the authentication capabilities provided to applications by OAuth via OIDC.

OAuth has several flows that can be used to perform user authentications and the most widely used and recommended flow is the Authorization code flow. Let’s take a quick look at how the authorization code flow works as it will help better understand the later parts of this article. Authorization code flow requires the app to redirect the user to the authorization server where the user will provide the required credentials and after successful authentication the authorization server will redirect the user back to the application along with an authorization code. The app would then exchange this authorization code for an access token along with an id_token (when using it for authentication via OIDC).


Image 1: OAuth 2 Authorization Code Flow

Disconnected Experience with Redirection

The main thing to note in the authorization code flow is that an application is required to redirect the user using a browser to the authorization server to perform the authentication. For web applications using OAuth, having this redirection to the authorization server’s login portal wouldn’t seem that much of an issue since the user is used to web pages switching in a web app. And given that the authorization server’s login portal can be themed similarly to the app the user might not even notice that he/she is leaving the web application and is actually in the authorization server. But when it comes to native applications this could be a disconnected experience for users. For example, the below video shows how the user experience looks while the user is logging into the iOS Github application.


If you watch the video above, you'll notice that the user receives a notification indicating redirection to github.com from the app and opts to proceed. Subsequently, it becomes evident that the login page opens within a web view, indicating that this login process is external to the app. Ideally, the app should have integrated this login screen natively.

Today, most apps access protected APIs once users are logged in, typically employing OAuth for security. Surprisingly, if you browse through the Apple App Store or Google Play Store, you'll find that most applications handle logins within the app itself. OAuth does offer the resource owner password credentials grant (password grant) for integrating username and password authentication in an API-centric manner without redirection, but it lacks support for MFA, social logins, or complex authentication requirements. Consequently, many apps rely on custom login solutions.

Is using custom login mechanisms problematic? Not necessarily, provided they are properly vetted. However, managing these across multiple platforms—iOS, Android, and web—is challenging, requiring consistent implementation across all applications. Centralized management of authentication logic would simplify this process, which OAuth facilitates to some extent, albeit requiring the authorization server to present the login UI.

In an ideal scenario, OAuth would support a flow allowing secure native application login integration. This gap is addressed by App-Native Authentication.

App-Native Authentication

App-Native Authentication is developed as an extension to the OAuth 2.0 protocol that enables developers to integrate the complete authentication flow within the application itself giving the end-user a secure and a seamless login experience.


Image 2: In-App Login Experience

App-native authentication provides a stateful and interactive authentication API that can be incorporated by the apps to perform user login in an API-centric manner and it is capable of handling both simple and complex authentication requirements. This includes authentication options such as username & password, passkeys, MFA, social login, and adaptive authentication. Furthermore, the authentication options available to the app and the authentication flow are decided in the server itself by using our login flow orchestration capabilities available in Asgardeo. This gives you the ability to centrally manage your login sequence and even incorporate new login options instantly into your application.

App-native authentication is a flow that starts with an OAuth2 authorization request and continues through an interactive stateful API ending with an authorization code that is exchangeable for a token.


Image 3: App-Native Authentication Flow

Extending OAuth

Let's take a look at how OAuth has been extended to support app-native authentication. We introduced the following:

  • A new response_mode called “direct” which serves as an indicator for the app-native authentication flow.
  • An authentication API that can handle complex authentication requirements supported by Asgardeo.
  • Ability to secure the authorization endpoint with OAuth client authentication.
  • The ability to use attestation capabilities provided by Google and Apple app stores to prove the authenticity of the application, which can be used where client authentication is not possible.

Image 4: Overall View with App-Native Authentication

Now, we will take a look at why each of the above was required and important.

The addition of the new response_mode called “direct” serves as an indicator to the server that the app is initiating an app-native authentication flow. As OAuth uses response modes to indicate how the authorization server responds to the client app, introducing a new response mode called “direct” seemed a natural way to go.

For applications to be able to embed the login to the app itself we require the login data (i.e. login options and sequence) to be relayed to the application and the data of the selected options relayed back to the server in an API centric manner. For this, we introduced a new authentication API that is capable of relaying and processing complex authentication flows. The authentication API is an interactive and a stateful API. Once the flow is started it will keep providing the data required for the next step as a response to each request and finally provide the app with an authorization code once all the authentication steps are completed. With the authentication API, we can handle almost all authentication scenarios in an API-centric manner which is possible through Asgardeo’s login portal in the redirect-based authentication modes. Furthermore, the authentication API provides metadata required for rendering the UI which gives the ability to incorporate dynamic authentication capabilities very efficiently.

The following diagram shows the login sequence for an app configured with MFA having username and password as the first step and TOTP as the second step.


Image 5: A 2FA App-Native Authentication Flow with Basic and TOTP Authentication

Let's take a look at how the actual API request and response flow would look like for the above sequence. Notice the request and response params such as reponse_mode=direct which is the flow indicator, the x-client-attestation header which contains the attestation object (we will discuss this further in the next section), the flowId received in the response and sent in the next request which is used to maintain states, the authentication options received related to each step and the metadata received in the response which can be used for UI representations in the app.

Request #1: Initiation using OAuth 2.0 authorization code flow with response_mode=direct

Response

Request #2: Username & password submission

Response

Request #3: TOTP submission

Response

So does this mean that everything can be done without involving a browser? Not quite. When it comes to social login or any federated authentication options the authentication is handled on the federated identity provider’s side. For the most part identity providers require that the user provide credentials in their login portal. However there are few providers such as Google, Facebook and Apple that provide native SDKs that can be used to incorporate login in a more user friendly manner than directing the user to their login portal in a browser. Taking this capability into account we have provided the capability to use the app-native authentication flow in two modes for federated authentication; the Native mode and the Redirect mode. In the native mode the app would directly use the identity provider SDKs and incorporate user login. And in redirect mode a constructed url is provided to the app to direct the user for authenticating with the particular identity provider in the browser.

This is a short demo of app-native authentication incorporated in an Android app. Here you will see 3 flows. The first one shows username & password + TOTP login. The second one shows Google login in native mode. And the third one shows login with a passkey.



Securing the Flow

Let's take a look at how the app-native authentication flow maintains the desired level of security. First and foremost app-native authentication should only be used with first-party apps. We can think of first-party apps as applications that belong to the same organization owning the authorization server, ex: A banking app provided by the bank and the users of the bank access it through login with the bank credentials. In a native authentication flow the users directly enter the credentials in the app, if the app belongs to an external organization then the user credentials will be visible to a 3rd party and the credentials could potentially be compromised.

As app-native authentication is an extension to OAuth it is possible to use the flow without any client authentication as a public client. However this is highly NOT recommended. The reason for this is that the app is susceptible to impersonation based attacks where the attacker will be able to impersonate the app and obtain valid user credentials as well as access tokens of the user. In contrast this is prevented in the redirection flow by the user directly providing the credentials to the authorization server's login portal where the credentials will not be visible to any other party. And obtaining a user access token by the malicious app is prevented due to the authorization server always transmitting the tokens to the app via a registered callback url (as opposed to directly transmitting it the app-native authentication flow). We have introduced two ways that can be used to mitigate this based on the type of app you have i.e. a confidential client or a public client.

Let’s first take a look at public clients as the primary app type for app-native authentication would be native apps which are generally considered as public clients. An app can be considered as a public client if it cannot keep credentials securely (i.e. in native apps the same app instance is provided to all users and it is possible to obtain any embedded credentials via various techniques such as reverse engineering the app). App hosting platforms; Google Play store and Apple App store provides the ability to obtain an attestation object for the app from the app store and it can be used to attest the authenticity of the app. We introduced the ability to use this attestation object as a means of proving the authentication request came from the genuine app. When using app attestation, the app should obtain the attestation object from the platform and pass it to Asgardeo along with the initial authorization call. Asgardeo will validate the attestation object through platform provided APIs. With attestation we make sure an attacker cannot impersonate the application as it is not possible for an impersonating app to obtain the attestation from the platform (Apple, Google).

For confidential clients, i.e. apps that can securely store a client credential we have provided the ability to secure the authorization endpoint with the same level of security available at the token endpoint. For example, if you have a web application and it uses client credential basic authentication for the token endpoint now that same authentication can be made mandatory at the authorization endpoint. For confidential clients as the client credential is stored securely in a manner that only the app can access, it is not possible for an attacker to impersonate the application.

After the initial authorization request is authenticated either via client attestation or client authentication the proceeding calls don’t require any authentication. They are bound to the initial request through a flowId. The flowId is unique to the authentication flow and is invalidated when the authentication flow is complete. Furthermore, it has a timeout attached to it. The flowId is transmitted only between the app and Asgardeo during the authentication flow and this communication should be protected with TLS (using https requests).

SSO and Logout

Just as login, single sign on (SSO) and logout are important. When it comes to SSO, Asgardeo relies on a cookie it creates to perform SSO the next time an authentication request is sent. This same cookie is set in an app-native authentication flow and without any difference from the redirect based authentication flow app’s can simply SSO if that cookie is preserved and transmitted during the initial request. However when it comes to native apps, they don't generally preserve cookies and therefore cookie based SSO is not possible. To address this we introduced a way to perform SSO based on a session identifier. The session identifier is available in the id_token received by the app after the initial authentication. Mobile apps can make use of secure shared storage capabilities provided by the app platform and share the session identifier within apps that need SSO. The app requiring SSO can simply attach the session identifier to the authorization request and it will perform SSO if the user has a valid session.

When it comes to logout, Asgardeo has implemented the OpenID Connect logout, but this requires that the app directs the user to the authorization server’s OIDC logout endpoint and then after the logout the user can be redirected back to the app. We extended the OIDC logout flow introducing a new param called response_mode=direct. Same as the login flow this param serves as an indicator to the server that the logout request should be treated in an api centric manner.

Alignment with Open Standards

When designing our solution for app-native authentication, we initially sought existing open standards, such as OAuth and OpenID Connect, to check whether this problem has already been solved. At the time, we found no finalized or draft standards available. We had our doubts whether extending OAuth was the best way to go as OAuth seemed to be more inclined to push users to interact with the authorization server itself. Due to our doubts, we also had discussions on creating a new communication protocol but from the get-go we knew whatever protocol we used or created should be able to provide an assertion that should be exchangeable for an OAuth token. This is because OAuth is used in most systems to protect resources and apps would generally need to access said resources after a user logs in. We were also researching other solutions that solved the problem and came across several vendors supporting API-centric authentication mechanisms in a proprietary manner without using any open standards.

During Internet Identity Workshop (IIW) 35, there was a session on “Enabling Native Mobile UX for OAuth/OpenID Connect flows,” which discussed the UX issues faced and how OAuth/OpenID Connect can address this. The conversations within the community showed that there is an interest in allowing users to directly interact with the apps for login while using OAuth. With the conversations at IIW35 and the fact that OAuth and OpenID Connect are widely adopted, and most apps requiring an OAuth token to access secured resources after the user login, we decided to extend OAuth for app-native authentication with plans on aligning with any future standard that comes out of the OAuth Working Group.

While developing app-native authentication we were also keeping an eye on the developments happening in the OAuth community. There have been discussions in the OAuth community about standardizing an api centric OAuth flow and as a result of community efforts an individual (draft) specification has been produced “OAuth 2.0 for First-Party Applications” which aims to provide a standard way to incorporate the OAuth layer for this along with an extensive guideline on maintaining the security of the flow. This is a promising specification and a much needed standard. We are keeping an eye on this and waiting till the specification is ready for adoption so we could look into aligning app-native authentication to this.

What's Next?

Having explored how OAuth can be extended to provide a secure and seamless login experience by integrating login capabilities natively within an app, what's next? We are committed to continually refining this flow based on user feedback and ongoing research. As of now, we have released an Android SDK that allows you to incorporate app-native authentication into your Android app with minimal effort. Currently, we are also actively developing SDKs for iOS and popular frameworks such as React and Flutter.

App-native authentication is supported in Asgardeo, Private Identity Cloud, and WSO2 Identity Server 7.0 and beyond. To delve deeper into app-native authentication and give it a try, visit our official documentation:

  • Asgardeo Documentation
  • WSO2 Identity Server Documentation
  • Mobile SDKs
English