Verifying permissions in UWP apps

UWP apps have been designed to be more secure and explicit about what they are allowed to do on the user’s device so that she is always in control. Hand in hand with that comes the fact that user can manually grant or deny individual permissions in the system Settings. However, this presents a challenge for developers – how can we make sure we have access to a capability when the user can deny it any time? That’s what I would like to show you in this article.

What can go wrong?

Let’s illustrate the issue on a straightforward example. Your app declares the Pictures library capability in Package.appxmanifest.

Pictures library capability
Pictures library capability

In the code you usually access the library and expect to work with it without issues.

var library = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);

Everything is bright and shiny until the user explicitly denies the permission in Settings -> Apps -> [App name] -> Advanced settings -> App permissions:

App permissions in Settings
App permissions in Settings

When we relaunch the app and try to access Pictures library we end up with an ugly UnauthorizedAccessException:

UnauthorizedAccessException
UnauthorizedAccessException

The solution

We will wrap access to the capability in a helper method, which will have appropriate logic to make sure our app handles the situation gracefully.

The first key element of our fix is not letting the app just crash when we lack the capability permission. A simple try...catch will do the trick:

private async Task<StorageLibrary> TryAccessLibraryAsync(KnownLibraryId library)
{
try
{
return await StorageLibrary.GetLibraryAsync(library);
}
catch (UnauthorizedAccessException)
{
}
}

Once we are in the catch statement, we know we have been denied access, and we need the user to grant us the required permission again. We can inform her via a MessageDialog:

//explain the issue
MessageDialog requestPermissionDialog =
new MessageDialog($"The app needs to access the {library}. " +
"Press OK to open system settings and give this app permission. " +
"If the app closes, reopen it afterwards. " +
"If you Cancel, the app will have limited functionality only.");
//setup dialog commands
var okCommand = new UICommand("OK");
requestPermissionDialog.Commands.Add(okCommand);
var cancelCommand = new UICommand("Cancel");
requestPermissionDialog.Commands.Add(cancelCommand);
requestPermissionDialog.DefaultCommandIndex = 0;
requestPermissionDialog.CancelCommandIndex = 1;
//display to user
var requestPermissionResult = await requestPermissionDialog.ShowAsync();

When the user chooses Cancel, we are merely returning null. Depending on how much our app relies on the permission we can decide to either keep the app running with limited functionality or just terminate it.

if (requestPermissionResult == cancelCommand)
{
//user chose to Cancel, app will not have permission
return null;
}

In case the user presses OK, we will navigate him to the advanced settings page for our app in the system Settings and give us the required permission using the special ms-settings:appsfeatures-app (see Docs).

//open app settings to allow users to give us permission
await Launcher.LaunchUriAsync(new Uri("ms-settings:appsfeatures-app"));
//confirmation dialog to retry
var confirmationDialog = new MessageDialog($"Please give this app the {library} permission " +
"in the Settings app which has now opened.");
confirmationDialog.Commands.Add(okCommand);
await confirmationDialog.ShowAsync();

This specific example handles check for library capability, but you can analogously implement it for other permissions as well.

Warning – don’t forget about user’s data

There is one catch with the presented solution – changing application permissions cannot be done at runtime. When the user modifies them in any way, the app will get terminated. That is the reason why the MessageDialog informs the user to reopen the app after granting the required permission. The key takeaway for your app is then the fact that you should always make sure all user data are safely saved before accessing a permission-bound API. This way you will be able to restore the user’s current action and will not break her flow when the app is reopened.

Alternatively, you can check for all permissions which are really required for your app to run properly at startup. The advantage of this is the fact that the user doesn’t have any work in progress at that point, so it is perfectly safe to close.

Source code

Full code of our helper method looks like this:

private async Task<StorageLibrary> TryAccessLibraryAsync(KnownLibraryId library)
{
try
{
return await StorageLibrary.GetLibraryAsync(library);
}
catch (UnauthorizedAccessException)
{
//explain the issue
MessageDialog requestPermissionDialog =
new MessageDialog(
$"The app needs to access the {library}. " +
"Press OK to open system settings and give this app permission. " +
"If the app closes, reopen it afterwards. " +
"If you Cancel, the app will have limited functionality only.");
var okCommand = new UICommand("OK");
requestPermissionDialog.Commands.Add(okCommand);
var cancelCommand = new UICommand("Cancel");
requestPermissionDialog.Commands.Add(cancelCommand);
requestPermissionDialog.DefaultCommandIndex = 0;
requestPermissionDialog.CancelCommandIndex = 1;
var requestPermissionResult = await requestPermissionDialog.ShowAsync();
if (requestPermissionResult == cancelCommand)
{
//user chose to Cancel, app will not have permission
return null;
}
//open app settings to allow users to give us permission
await Launcher.LaunchUriAsync(new Uri("ms-settings:appsfeatures-app"));
//confirmation dialog to retry
var confirmationDialog = new MessageDialog(
$"Please give this app the {library} permission " +
"in the Settings app which has now opened.");
confirmationDialog.Commands.Add(okCommand);
await confirmationDialog.ShowAsync();
}
return null;
}

You can find the example solution for this article on my GitHub.

Summary

We have seen how we can gracefully handle the situation when user has denied us permission we require for our app to run properly. This way we offer excellent user experience and can even specifically explain why the given API is needed.

 

Buy me a coffeeBuy me a coffee

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.