Need to add Offline and Touch ID to Microsoft Authentication Layer

北城以北 提交于 2019-12-11 15:38:16

问题


I have some Xamarin C# code that authenticates users against the company directory, it's basically the code found in the Microsoft tutorial, and right now we're iOS-only:

App.xaml.cs

PublicClientApplicationOptions options = new PublicClientApplicationOptions()
{
    ClientId = MyAppClientId,
    TenantId = MyAppTenantId
};
var builder = PublicClientApplicationBuilder.CreateWithApplicationOptions(options);
if (!string.IsNullOrEmpty(iOSKeychainSecurityGroup))
{
    builder = builder.WithIosKeychainSecurityGroup(iOSKeychainSecurityGroup);
}
PCA = builder.Build();

ViewModel.cs

string Scopes = "User.Read";
var scopes = Scopes.Split(' ');  // Yeah, overkill

// First, attempt silent sign in
// If the user's information is already in the app's cache,
// they won't have to sign in again.
string accessToken = string.Empty;
try
{
    var accounts = await App.PCA.GetAccountsAsync();
    // PCA.GetAccountsAsync() returned [List<IAccount> #=0]
    if (accounts.Count() > 0)
    {
       var silentAuthResult = await App.PCA
          .AcquireTokenSilent(scopes, accounts.FirstOrDefault())
          .ExecuteAsync();
       accessToken = silentAuthResult.AccessToken;
    }
 }
 catch (MsalUiRequiredException)
 {
    // This exception is thrown when an interactive sign-in is required.
    // Don't need to do anything, we will notice the empty access token later
 }

 if (string.IsNullOrEmpty(accessToken))
 {
     // Prompt the user to sign-in
     var interactiveRequest = App.PCA.AcquireTokenInteractive(scopes);
     // PCA.AcquireTokenInteractive(scopes) returned Microsoft.Identity.Client.AcquireTokenInteractiveParameterBuilder
     if (authUiParent != null)
     {
        interactiveRequest = interactiveRequest
           .WithParentActivityOrWindow(authUiParent);
     }

     try
     {
        var authResult = await interactiveRequest.ExecuteAsync();
     }
     catch (MsalClientException clientException)
     {
        // When I entered the wrong password, and then hit cancel:
        // Or, when I got the "you need admin permissions" error and then hit cancel:
        /*
        interactiveRequest.ExecuteAsync() threw MsalClientException [error code "authentication_canceled"]
        Exception MsalClientException: User canceled authentication.
        */
     }
  }

That's working great, now I need it to be slightly different (of course).

1) First of all, we need an "offline" mode. If the user wants to access the app in a place that has no Internet, we want them to enter their username and password which will be compared against the last known good value for that user. Right now we are using an internal encrypted database to store last known good values for comparison. Yes, there is a security hole here, if we've disabled a user's account on the server they can continue to log in as long as they disable the Internet on their mobile device - we have other ways to minimize this problem that I won't go into here.

2) Secondly, we want to allow touch ID. But even if the fingerprint verifies the identify of the user, we still want to check if that user has been disabled at the server - so we need to send the "last known good values" for that user to the server for verification. Of course, if there is no Internet, the fingerprint will be enough. I'm assuming this means we need a screen before calling AcquireTokenInteractive() where we give the user a chance to use Touch ID, with a button that says "nope, I want to type in my username and password please"

3) Finally, even when we have Internet and the user has chosen to not use Touch ID, we want them to enter their password every time. We want to remember the username of the most-recently-logged-in user and fill that in for them to speed things up, but for security reasons we need a password every time.


回答1:


It seems security of your application is your biggest concern. For this reason I'll go in to some detail on security and identity to help your decision making.

Issue 1: Offline Mode

I'm afraid the approach you've taken for offline access isn't advisable. In fact, in MSAL we actively mitigate it by using the System Browser.

Security Issues with Embedded Browsers and Forms Based Authentication in Mobile Applications

When a user signs in to a mobile application using either an embedded webview or a form based authentication, that application can access the plain text username and password entered in to the application. If a user downloads an application that is acting like a legitimate application from your company, those credentials can be stolen without the user being aware. This is a security vulnerability, and one that is severe enough that Google has taken the step of blocking all apps that use embedded webview or forms based authentication.

System Browsers and Authentication

To prevent this storage of credentials by applications Google, Microsoft, and others have switched to the System Browser to collect credentials. We use the new ability of the operating system on mobile devices to display a web sign-in experience on top of your application. This appears native to the user but is actually the browser of the operating system. Because neither the application nor Microsoft has access to the browser of the operating system, the credentials entered by your users are safe. You'll see most modern applications using this pattern.

This will also prevent you from storing username and password of your users and is by design. Please don't store your user's credentials anywhere.

How To Allow Offline Access for Applications

To achieve your scenario correctly there are two options we've seen apps use:

  • The most popular pattern we've seen is for applications to ask the user to set a PIN to access the application when they first sign-in or as an option in Settings. Often times the user is asked to set a PIN as well as Touch ID/Face ID if Touch ID/Face ID fails or is reset. The user then uses the PIN to access the app on each launch, and this also works when internet isn't available. This PIN is stored securely and encrypted on the device. As soon as the internet is available the application should call acquireTokenSilently() to ensure the user still has access to the resource. If they don't, you should prompt the user to sign in again. Many banks and other highly regulated industries use this pattern as the best compromise between user experience and security.

  • The next option is to use the resiliency we've built in to the library for companies and application developers that want the user to maintain access to resources in case of an outage. We allow companies and application developers to configure a token lifetime that is longer than our default token lifetime for access tokens and refresh tokens. When a user isn't able to access the internet to get a new access token, the extended lifetime can allow the user to continue to use the application.

    This has the added benefit of working if your APIs are local to your environment but the identity provider is cloud based or if Azure AD is suffering an outage but the rest of the internet is working. Your internal APIs will accept the tokens so your users can continue to work even if the internet is inaccessible. Both the API and your app will need to use MSAL in order for your API to honor the extended lifetime specified in the token. Keep in mind we have maximum value for the access token of one day. If your internet outage is longer than that, I recommend using option 1 above.

Issue 2: Check User With Identity Service When Using Touch ID

This answer is fairly easy. Just use acquireTokenSilently() after the Touch ID is used. This will acquire a new token for the resource you requested the token for. For default token values, the access token will be refreshed every 1 hour. I do not recommend forcing authenticating every Touch ID as that will introduce significant slowdown and cellular usage. If you just want to check the user is present, I recommend you use a locally stored PIN as discussed above.

Issue 3: Force Authentication if Touch ID is not enabled

You can force authentication at any time by using acquireToken()with the appropriate values. See the code sample here for the method acquireTokenInteractively().

However, a small amount of warning here on usability:

The Problem With Using Password Frequently To Test User Access

The identity service is used to check if a user has access to the resources, not to check if the user has access to the device or if the user is still the user from a previous session. For that you should use the PIN method I discuss above, along with Touch ID/Face ID if available. Why?

For true security, a password should be combined with 2FA. Research indicates that 99% of illegal access can be mitigated by 2FA. However, when a user has to use 2FA for a sign-in it increases the friction they have to do endure to sign-in. In fact, relying on passwords at all is problematic.

Your use case seems to indicate you want the maximum amount of security for your application. That necessitates using 2FA if possible. However, moving to the best security posture for your application would also make using a password at every app launch very difficult for your users.

The recommended pattern would again be PIN or Touch ID/Face ID along with a password prompt after either a change to the user's access to a resource or after a long period of time.



来源:https://stackoverflow.com/questions/58082960/need-to-add-offline-and-touch-id-to-microsoft-authentication-layer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!