Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Web API 2: Login with external provider via native mobile (iOS) app

I have done much searching and have not been able to find an ideal solution for this issue. I know that there is an alleged solution (WebApi ASP.NET Identity Facebook login) however, some elements of the solution are a (in my mind) seriously hacky (e.g. registering the user with a regular account and then adding the external login, rather than registering them with the external login).

I would like to be able to register and authenticate against an ASP.NET Web API 2 application, after already having used the Facebook SDK login on a iOS mobile app, i.e. I have already authenticated against Facebook using their SDK, and now want to seamlessly register/authenticate with the ASP.NET Web API. I do not want to use the process where I have to use the web calls (/api/Account/ExternalLogin) as this, well, is not a great user experience on a native mobile app.

I have tried learning about OWIN, but the .NET framework is complex and I am struggling in how to solve this issue.

like image 748
David Poxon Avatar asked Aug 14 '14 07:08

David Poxon


1 Answers

I needed to do this today for my Ionic app. The Web API Account controller has its own opinion on how to do things and the best way to understand it is reading this pretty amazing 3 part blog post by Dominick Baier. https://leastprivilege.com/2013/11/26/dissecting-the-web-api-individual-accounts-templatepart-3-external-accounts/.

The way I worked around it was to forget the out-of-the-box flow, but instead use the accessToken from the native Facebook login and then call into the following server code to 1) call the Facebook API to validate the access token, 2) from that Facebook call, get the email and id, 3) either get the user or create it (and login) which is already code that's in the Account controller in other places, 4) Create the local authority JWT for subsequent Web API calls.

public class ProviderAndAccessToken {
    public string Token { get; set; }
    public string Provider { get; set; }
}

[AllowAnonymous]
[HttpPost]
[Route("JwtFromProviderAccessToken")]
public async Task<IHttpActionResult> JwtFromProviderAccessToken(ProviderAndAccessToken model) {
    string id = null;
    string userName = null;

    if (model.Provider == "Facebook") {
        var fbclient = new Facebook.FacebookClient(model.Token);
        dynamic fb = fbclient.Get("/me?locale=en_US&fields=name,email");
        id = fb.id;
        userName = fb.email;
    }

    //TODO: Google, LinkedIn

    ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(model.Provider, id));
    bool hasRegistered = user != null;

    string accessToken = null;
    var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
    var props = new AuthenticationProperties() {
        IssuedUtc = DateTime.UtcNow,
        ExpiresUtc = DateTime.UtcNow.Add(Startup.OAuthOptions.AccessTokenExpireTimeSpan),
    };

    if (hasRegistered) {
        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
            OAuthDefaults.AuthenticationType);
        ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
            CookieAuthenticationDefaults.AuthenticationType);

        AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
        Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);


        identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
        identity.AddClaim(new Claim("role", "user"));

    }
    else {

        user = new ApplicationUser() { UserName = userName, Email = userName };

        IdentityResult result = await UserManager.CreateAsync(user);
        if (!result.Succeeded) {
            return GetErrorResult(result);
        }

        result = await UserManager.AddLoginAsync(user.Id, new UserLoginInfo(model.Provider, id));
        if (!result.Succeeded) {
            return GetErrorResult(result);
        }

        identity.AddClaim(new Claim(ClaimTypes.Name, userName));
    }

    identity.AddClaim(new Claim("role", "user"));
    var ticket = new AuthenticationTicket(identity, props);
    accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);

    return Ok(accessToken);
}

The code I'm using in Ionic basically does this to get the access token from Facebook, then call the Web API to get a local authority JWT to use as the Bearer token.

Facebook.login(['public_profile', 'email']).then((result) => {
    return this.http.post("<URL>/api/Account/JwtFromProviderAccessToken", { provider: "Facebook", token: result.authResponse.accessToken })
        .map((res: Response) => res.json())
        .catch(this.handleError)
        .subscribe((res: Response) => {
            // use the result as the Bearer token
        });
})...

Seems pretty safe, but understand that I'm not security expert so this code comes without warranty and please let me know if you see anything glaring and I'll update the code.

like image 150
Sean Chase Avatar answered Nov 08 '22 06:11

Sean Chase