Using token-based authentication with ArcGIS Runtime

Using token-based authentication with ArcGIS Runtime

Example authentication UI in WPF

Often you need to implement some sort of authentication on your applications that are relying on some content from ArcGIS Online (or Portal). In most of my applications that are used as proof of concepts, demos or if I’m authenticating against ArcGIS Server directly, I will use token-based authentication model.

In this post, I will show how to make use of token-based authentication in WPF application using ArcGIS Runtime SDK for .NET. I will use ArcGIS Online as a backend since I don’t have others available. I will be covering OAuth authentication model on upcoming posts.

TIP: ArcGIS Runtime SDK for .NET – Toolkit has SignInForm-component in preview. This is available currently only in WPF though but you might want to have a look into this as well.

What is token-based authentication

Token-based: Your app provides a valid user name and password for the user. In the response, you receive a token that is included with requests for secured content on the portal for authenticated resources. Available with ArcGIS Online and ArcGIS Enterprise version 10.2 or earlier later.


https://developers.arcgis.com/documentation/core-concepts/security-and-authentication/

Basically, you send your user name and password to the /generateToken endpoint. The token is an encrypted string that contains the user’s name, token expiration time and some other information. After getting the token from the service, you will attach that to the requests accessing that secured resource.

The structure of the generate token url on ArcGIS Server:

https://<host>:<port>/<site>/tokens/generateToken

Here is the URL that you can use if you are authenticating against the ArcGIS Online:

https://www.arcgis.com/sharing/rest 

Key parts of the authentication

AuthenticationManager is a helper class that is used to manage all the credentials in ArcGIS Runtime. This simplifies the code required in your application a lot. It will abstract the low-level details on how to work with secured resources and attach the credential automatically to the requests when necessary.

In the case where you want explicitly invoke the authentication, you can use GenerateCredentialAsync-method. If you want to react situations where the user doesn’t have access to the resource (an error is returned from the resource) you can use ChallengeHandler.

// Authenticate against target resource
var userCredentials = await AuthenticationManager.Current.GenerateCredentialAsync(
		new Uri(PortalUrl),
		username, 
		password);							

After the credentials are returned, you want to cache the credential so AuthenticationManager can attach that to the requests that are going to the resource you authenticated against.

// Make sure that the credentials are stored for later use
if (!AuthenticationManager.Current.Credentials.Contains(userCredentials))
	AuthenticationManager.Current.AddCredential(userCredentials);

You can catch the authentication errors by wrapping GenerateCredentialAsync in Try-Catch and look for ArcGISWebExceptions.

try
{
    // Clear previous error message
    ErrorMessage = string.Empty;

    // Authenticate against target resource 
    var userCredentials = await AuthenticationManager.Current.GenerateCredentialAsync(
                    new Uri(PortalUrl),
                    username, 
                    password);
		
    // Use credentials	
}
catch (ArcGISWebException ex)
{
  ErrorMessage = ex.Details.First();
}
catch (Exception ex)
{
  ErrorMessage = "Authentication failed due unexpected error.";
}

When the user is logging out, you want to clear the currently cached credentials.

// Clear previously saved credentials due logout
AuthenticationManager.Current.RemoveAllCredentials();

Example scenario

Example scenario
  1. When the application is started, UI is shown that requires the user to set her username and password to sign in. Authentication is done against ArcGIS Online.
  2. If the authentication is a failure, the error is shown in the authentication UI.
  3. If the authentication is a success,
    1. the application queries web maps from the ArcGIS Online and shows them in a list.
    2. the application shows user profile (profile picture, full name, username) on the top right corner of the application. These are returned from the ArcGIS Online, when the connection is created with valid credentials.
    3. user can click the profile to get access to signin out or switching an account.

There are other authentication workflows such as ask credentials only if the user is accessing a resource that requires authentication. In this scenario, I wanted to control the application flow in more detail. Since I’m only accessing a single portal, I’m assuming that all the resources that would require different credentials are federated through the portal.

Implementation

The sample is written using WPF with some libraries, mainly Prism.WPF and MaterialDesignThemes, to make things a bit easier.

See the full implementation in GitHub. The code might evolve over time.

Login and connecting to the ArcGIS Online

The LoginView is very straight forward. Basically, I’m just binding values to the ViewModel.

Using PasswordBox and how to pass the password as a SecureString to the ViewModel is based on this sample. When the user clicks “Sign in“-button, LoginCommand gets invoked in the ViewModel. The parameter that is passed in implements the IHavePassword-interface, which provides a way to get the password value out as a SecureString and a way to clear the stored password from the memory.

IsSigningIn-property controls if the overlay is shown in the UI. ErrorMessage-property provides potential error information to the user.

If the authentication is a success, I will create new ArcGISPortal-instance with the user credentials. ArcGISPortal automatically signs in to the organisation that the user belongs to. It will also populate the ArcGISPortal.User-property that is used later to access the user profile.

After the portal instance is created, I will publish UserSessionCreatedEvent using EventAggregator. EventAggregator is used to provide loosely coupled communication between different ViewModels. When the UserSessionCreatedEvent is published, other parts can react to the event and access the ArcGISPortal instance that was passed on.

private async void Login(IHavePassword parameter)
{
    try
    {
        if (IsSigningIn) return;
        IsSigningIn = true;
        ErrorMessage = string.Empty;

        var userCredentials = await AuthenticationManager.Current.GenerateCredentialAsync(
            new Uri(PortalUrl),
            UserName, 
            ConvertToUnsecureString(parameter.Password));

         //TODO: if remember me is checked, save creadentials to the Windows Credential Vault

         // Make sure that the credentials are saved for later use
         if (!AuthenticationManager.Current.Credentials.Contains(userCredentials))
             AuthenticationManager.Current.AddCredential(userCredentials);

         var portal = await ArcGISPortal.CreateAsync(new Uri(PortalUrl), userCredentials, CancellationToken.None);
         EventAggregator.GetEvent<UserSessionCreatedEvent>().Publish(
             new UserSessionCreatedMessage(portal));
    }
    catch (ArcGISWebException ex)
    {
         ErrorMessage = ex.Details.First();
    }
    catch (Exception ex)
    {
        ErrorMessage = "Authentication failed due unexpected error.";
    }
    finally
    {
        IsSigningIn = false;
        parameter.ClearPassword();
    }
}

User profile and logout

User profile

The UserProfileView is using PopupBox-control (MaterialDesignTheme) and the controls inside it directly bind to the ViewModel properties and commands.

The UserProfileViewModel subscribes to the UserSessionCreatedEvent. When the event is published, the view model will get the required information from the ArcGISPortal.User-property.

public UserProfileViewModel(IEventAggregator eventAggregator) : base(eventAggregator)
{
    LogoutCommand = new DelegateCommand(Logout);
    SwitchUserCommand = new DelegateCommand(SwitchUser);

    EventAggregator.GetEvent<UserSessionCreatedEvent>().Subscribe(args => 
    {
        FullName = args.Portal.User.FullName;
        UserName = args.Portal.User.UserName;
        ProfilePicture = args.Portal.User.ThumbnailUri;
    });
}

When the user clicks either “Switch Account”-button or “Log Out”-button, UserSessionEndedEvent is published. The event contains the information if the user wants to just log out or to switch users.

private void Logout()
{
    EventAggregator.GetEvent<UserSessionEndedEvent>().Publish(
        new UserSessionEndedMessage(UserSessionEndedMessage.SessionEndingType.Logout));
}

When user decided to log out, the LoginViewModel will react to the UserSessionEndedEvent and clear existing credential cache.

EventAggregator.GetEvent<UserSessionEndedEvent>().Subscribe(args =>
    {
        if (args.EndingType == UserSessionEndedMessage.SessionEndingType.SwitchUser)
            UserName = string.Empty;
        AuthenticationManager.Current.RemoveAllCredentials();
    });

Summary

Using token-based authentication in the ArcGIS Runtime is straight forward and it provides a lot of freedom to build the user experience around it. This is really where the most of the implementation time goes and is often depended on the general application flow.

Links

Leave a Reply