« Back to home

Debugging Azure Notification Hub Registrations

Posted on

On a recent project I had the fun task of investigating why Azure Notification Hubs wasn't playing nicely with an mobile app migrated from Azure Mobile Services to the newer App Services model. Everything seemed to be working on the surface of things but no notifications were being delivered.

First I checked the Azure portal and it showed my active registered devices but no push notifications.
No notifications

So I knew my client side application was still registering with Azure Notifications Hub without any issues however for some reason the notifications weren't going out which made me suspect it was the tags we were registering.

Our Push Notifications

A key to understanding why our push notifications were failing is knowing how we decided to setup our tags and how we were targetting users for push notification. We elected to use the out of the box functionality for registration and then we targetting push notifications using the in built UserId tag that is generated. I needed to be sure that the tags were still working but the portal doesnt show any detail on tag registration.

To work around this I decided to develop a Diagnostic Tool to retrieve the registrations and show me thier tags and template details. With this tool I was able to find both a new and an old registration and the problem was imediately obvious.

The UserId for external providers used to be coming in the following format:
_UserId:Facebook:XXX0143676XXXXXXX

But now they were coming in with a completely different format
_UserId:sid:89e791b3b64a68c1d270190cdd153efc

But why?

Broken Tag Registrations?

First, A little bit of background on how the .NET backend server SDK for Azure Mobile Apps works.

The server quickstart from the Azure portal calls UseDefaultConfiguration(). This equivalent to the following setup:

new MobileAppConfiguration()
    .AddMobileAppHomeController()           
    .MapApiControllers()
    .AddTables(                               
            .MapTableControllers()
            .AddEntityFramework()            
        )
    .AddPushNotifications()                 
    .MapLegacyCrossDomainController()    
    .ApplyTo(config);

This line here: AddPushNotifications() is what is of interest to me, I dug a little deeper to discover that this method registers a controller that is responsible for handling the registration with Azure Notification Hubs.

if (config == null)
    throw new ArgumentNullException("config");

config.GetMobileAppConfiguration()
      .AddBaseRouteExclusion("notificationinstallations");

config.Routes.MapHttpRoute("NotificationInstallations", "push/installations/{installationId}", (object) new
  {
    controller = "notificationinstallations"
  });

Internally to this controller is where I found the answer. If the user is logged in then the name claim is retrieved from the claims token.

Claim first = serviceUser.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
if (first != null)
   installation.Tags.Add(string.Format("_UserId:{0}", (object) first.Value));

A little research revealed that the Mobile Services auth used the native auth providers UserId while App Services uses a sid generated by easy auth. The solution to minimise the changes required.

Fixed Tag Registrations

It turns out that the original native UserId is still available to use an we can retrieve it with the following code:

    public static async Task<string> GetNativeUserIdAsync(this ClaimsPrincipal principal, HttpRequestMessage request)
    {
        ProviderCredentials creds;
        var providerId = GetProvider(principal);
        switch (providerId.ToLower())
        {
            case "facebook":
                creds = await principal.GetAppServiceIdentityAsync<FacebookCredentials>(request);
                break;
            case "microsoftaccount":
                creds = await principal.GetAppServiceIdentityAsync<MicrosoftAccountCredentials>(request);
                break;
            case "twitter":
                creds = await principal.GetAppServiceIdentityAsync<TwitterCredentials>(request);
                break;
            case "google":
                creds = await principal.GetAppServiceIdentityAsync<GoogleCredentials>(request);
                break;
            default:
                return principal.GetPrincipalName();
        }
        var nameClaim = creds.UserClaims.SingleOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");

        if (nameClaim == null)
        {
            return string.Empty;
        }
        return $"{creds.Provider}:{nameClaim.Value}";
    }
}

Using this original native UserId I was able to modify the tags included in the registration and minimise the other areas of the application that needed to be updated.