Michael Phillips
Michael Phillips

Reputation: 862

Dropnet authentication behaviour on Windows Phone 7.1 and 8.0

I have a pair of Windows Phone apps that make use of Dropnet for Dropbox integration. One developed in Visual Studio Express 2012 targeting 7.1 and one in Visual Studio 2013 targeting 8.0.

Both are using Dropnet 1.9.6 and RestSharp 104.4.0 via NuGet.

The 7.1 project uses the following static class to interact with DropNet authentication (and file operations but I've excluded them for clarity).

public static class DropboxStorage
{
    const string UserToken = "UserToken";
    const string UserSecret = "UserSecret";

    private static DropNetClient _Dropbox;
    public static DropNetClient Dropbox
    {
        get
        {
            if (_Dropbox == null)
            {
                _Dropbox = new DropNetClient(AppKey, AppSecret);
                if (IsAuthenticated)
                {
                    _Dropbox.UserLogin = new UserLogin
                    {
                        Token = (string)IsolatedStorageSettings.ApplicationSettings[UserToken],
                        Secret = (string)IsolatedStorageSettings.ApplicationSettings[UserSecret]
                    };
                }
                _Dropbox.UseSandbox = true;
            }
            return _Dropbox;
        }
    }

    public static bool IsAuthenticated
    {
        get
        {
            return IsolatedStorageSettings.ApplicationSettings.Contains(UserToken) &&
                IsolatedStorageSettings.ApplicationSettings.Contains(UserSecret);
        }
    }

    public static void StartAuthentication(Action<Uri> success, Action<Exception> failure)
    {
        Dropbox.GetTokenAsync(userLogin =>
        {
            var url = Dropbox.BuildAuthorizeUrl(userLogin);
            if (success != null) success(new Uri(url, UriKind.RelativeOrAbsolute));
        }, error =>
        {
            if (failure != null) failure(error);
        });
    }

    public static void CheckAuthentication(Uri uri, Action success, Action<Exception> failure)
    {
        if (uri.LocalPath == "/1/oauth/authorize_submit")
        {
            Dropbox.GetAccessTokenAsync((accessToken) =>
            {
                IsolatedStorageSettings.ApplicationSettings[UserToken] = accessToken.Token;
                IsolatedStorageSettings.ApplicationSettings[UserSecret] = accessToken.Secret;
                IsolatedStorageSettings.ApplicationSettings.Save();
                if (success != null) success();
            },
            (error) =>
            {
                if (failure != null) failure(error);
            });
        }
    }

This is then accessed via a page with a WebBrowser control.

XAML

<phone:WebBrowser x:Name="Browser"
                              IsScriptEnabled="True"
                              Navigated="Browser_Navigated"/>

C#

public partial class DropboxPage : PhoneApplicationPage
{
    public DropboxPage()
    {
        InitializeComponent();

         DropboxStorage.StartAuthentication(
                uri => Browser.Navigate(uri),
                ex => ShowException(ex));
     }

    public void Browser_Navigated(object sender, NavigationEventArgs e)
    {
        Debug.WriteLine(e.Uri.OriginalString);
        DropboxStorage.CheckAuthentication(
            e.Uri,
            () => NavigationService.GoBack(),
            ex => ShowException(ex));
    }

    private void ShowException(Exception e)
    {
        MessageBox.Show(e.Message);
        NavigationService.GoBack();
    }
}

The Dropbox authentication web pages are displayed

and the code determines that authentication has been successful when the Dropbox url contains /1/oauth/authorize_submit.

This all works perfectly, however the 8.0 project behaves a little differently.

The first difference is the url returned by the BuildAuthorizeUrl call.

The second difference is that the Dropbox url doesn't change from /authorize?oauth_token= to /authorize_submit once the app has been connected so GetAccessTokenAsync is never called and the user token and user secret aren't stored.

My investigations to date suggest a couple of ways to fix this that aren't ideal

  1. Remove the url validation from around the GetAccessTokenAsync call and eat the resulting DropboxExceptions
  2. Add a callback url to the initial BuildAuthorizeUrl call.

I'm currently doing 1. just to get things up and running but obviously this isn't a long term solution. I'd prefer to avoid using a callback url as it seems like an over complication for a mobile app.

What I'm after is a way of getting the 8.0 project to behave in the same way as the 7.1 project. I've had a look at the Dropnet source code and that contains https://api.dropbox.com as it's base url so I'm even less clear as to how the 7.1 code is working in the first place.

Upvotes: 1

Views: 254

Answers (2)

Michael Phillips
Michael Phillips

Reputation: 862

The difference in behaviour is caused by the 8.0 project using System.Windows.Interactivity to convert the Navigated event into a call to a Command on the ViewModel. This seems to prevent the website and WebBrowser control interacting correctly and redirecting to https://www.dropbox.com.


Current

XAML

<phone:WebBrowser x:Name="Browser"
                  IsScriptEnabled="True"
                  Visibility="{Binding IsAuthenticated, Converter={StaticResource BoolToInvisibilityConverter}}"
                  Source="{Binding AuthenticateUri}">               
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Navigated" >
            <i:InvokeCommandAction Command="{Binding BrowserNavigated}" 
                                    CommandParameter="{Binding Source, ElementName=Browser}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</phone:WebBrowser>

C#

BrowserNavigated = new RelayCommand<Uri>(u =>
                {                   
                    RemoteStorage.CheckAuthentication(u, () => IsAuthenticated = true, ex => ShowException(ex));
                });

Corrected

XAML

        <phone:WebBrowser x:Name="Browser"
                          IsScriptEnabled="True"
                          Visibility="{Binding IsAuthenticated, Converter={StaticResource BoolToInvisibilityConverter}}"
                          Source="{Binding AuthenticateUri}"
                          Navigated="Browser_Navigated"/>

C#

    private void Browser_Navigated(object sender, NavigationEventArgs e)
    {
        ViewModel.BrowserNavigated.Execute(e.Uri);
    }

In both cases the AuthenticateUri property of the view model bound to the WebBrowser Source is set by the StartAuthentication method in the DropboxStorage class.

Having added the event handler the 8.0 project works as expected.

Upvotes: 1

Austin Thompson
Austin Thompson

Reputation: 2281

I also started seeing this problem not too long ago. My app worked fine on WP8 up until that point. I don't remember all the details but I looked at my checkin to fix it and I think an extra page load for the first url started happening. No warning from Dropbox about the change either.

To solve it I wait for a certain url to show up in the LoadCompleted event. You use the Navigated event which I think should also work.

I would modify your event handler like this:

public void Browser_Navigated(object sender, NavigationEventArgs e)
{
    if (e.Uri.AbsolutePath == "/1/oauth/authorize_submit")
    {
        DropboxStorage.CheckAuthentication(
            e.Uri,
            () => NavigationService.GoBack(),
            ex => ShowException(ex));
    }
}

Upvotes: 0

Related Questions