0
votes

I have Web API and the client that calls the web API in separate solutions. I need to authenticate users using local authentication with existing user data and also authenticate with external authentication like Google and Facebook.

Local Authentication works fine. But while authenticating external logins, I'm getting unauthorized response when calling api/Account/UserInfo. Please note that I'm able to generate the token from google account correctly.

Here's my AccountController.cs file.

[Authorize]
[RoutePrefix("api/Account")]
[EnableCors("*","*","*")]
public class AccountController : ApiController
{
    private const string LocalLoginProvider = "Local";
    private ApplicationUserManager _userManager;

    public AccountController()
    {
    }

    public AccountController(ApplicationUserManager userManager,
        ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
    {
        UserManager = userManager;
        AccessTokenFormat = accessTokenFormat;
    }

    public ApplicationUserManager UserManager
    {
        get
        {
            return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
        private set
        {
            _userManager = value;
        }
    }

    public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }

    // GET api/Account/UserInfo
    [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
    //[AllowAnonymous]
    [Route("UserInfo")]
    public UserInfoViewModel GetUserInfo()
    {
        ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

        return new UserInfoViewModel
        {
            Email = User.Identity.GetUserName(),
            HasRegistered = externalLogin == null,
            LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
        };
    }

    // POST api/Account/Logout
    [Route("Logout")]
    public IHttpActionResult Logout()
    {
        Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
        return Ok();
    }

    // GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
    [Route("ManageInfo")]
    public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
    {
        IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());

        if (user == null)
        {
            return null;
        }

        List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();

        foreach (IdentityUserLogin linkedAccount in user.Logins)
        {
            logins.Add(new UserLoginInfoViewModel
            {
                LoginProvider = linkedAccount.LoginProvider,
                ProviderKey = linkedAccount.ProviderKey
            });
        }

        if (user.PasswordHash != null)
        {
            logins.Add(new UserLoginInfoViewModel
            {
                LoginProvider = LocalLoginProvider,
                ProviderKey = user.UserName,
            });
        }

        return new ManageInfoViewModel
        {
            LocalLoginProvider = LocalLoginProvider,
            Email = user.UserName,
            Logins = logins,
            ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
        };
    }

    // POST api/Account/ChangePassword
    [Route("ChangePassword")]
    public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
            model.NewPassword);

        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }

        return Ok();
    }

    // POST api/Account/SetPassword
    [Route("SetPassword")]
    public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);

        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }

        return Ok();
    }

    // POST api/Account/AddExternalLogin
    [Route("AddExternalLogin")]
    public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);

        AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);

        if (ticket == null || ticket.Identity == null || (ticket.Properties != null
            && ticket.Properties.ExpiresUtc.HasValue
            && ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
        {
            return BadRequest("External login failure.");
        }

        ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);

        if (externalData == null)
        {
            return BadRequest("The external login is already associated with an account.");
        }

        IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
            new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));

        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }

        return Ok();
    }

    // POST api/Account/RemoveLogin
    [Route("RemoveLogin")]
    public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        IdentityResult result;

        if (model.LoginProvider == LocalLoginProvider)
        {
            result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
        }
        else
        {
            result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
                new UserLoginInfo(model.LoginProvider, model.ProviderKey));
        }

        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }

        return Ok();
    }

    // GET api/Account/ExternalLogin
    [OverrideAuthentication]
    [HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
    [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
    [AllowAnonymous]
    [Route("ExternalLogin", Name = "ExternalLogin")]
    public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
    {
        if (error != null)
        {
            return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
        }

        if (!User.Identity.IsAuthenticated)
        {
            return new ChallengeResult(provider, this);
        }

        ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

        if (externalLogin == null)
        {
            return InternalServerError();
        }

        if (externalLogin.LoginProvider != provider)
        {
            Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
            return new ChallengeResult(provider, this);
        }

        ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
            externalLogin.ProviderKey));

        bool hasRegistered = user != null;

        if (hasRegistered)
        {
            Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);

            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);
        }
        else
        {
            IEnumerable<Claim> claims = externalLogin.GetClaims();
            ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
            Authentication.SignIn(identity);
        }

        return Ok();
    }

    // GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
    [AllowAnonymous]
    [Route("ExternalLogins")]
    public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
    {
        IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
        List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();

        string state;

        if (generateState)
        {
            const int strengthInBits = 256;
            state = RandomOAuthStateGenerator.Generate(strengthInBits);
        }
        else
        {
            state = null;
        }

        foreach (AuthenticationDescription description in descriptions)
        {
            ExternalLoginViewModel login = new ExternalLoginViewModel
            {
                Name = description.Caption,
                Url = Url.Route("ExternalLogin", new
                {
                    provider = description.AuthenticationType,
                    response_type = "token",
                    client_id = Startup.PublicClientId,
                    redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
                    state = state
                }),
                State = state
            };
            logins.Add(login);
        }

        return logins;
    }

    // POST api/Account/Register
    [AllowAnonymous]
    [Route("Register")]
    public async Task<IHttpActionResult> Register(RegisterBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var user = new ApplicationUser()
        {
            UserName = model.Email,
            Email = model.Email,
            FirstName = model.FirstName,
            LastName = model.LastName,
            CellNumber = model.CellNumber,
            CompanyName = model.CompanyName,
            Address = model.Address,
            City = model.City,
            Country = model.Country,
            State = model.State,
            Zip = model.Zip,
            EmailConfirmed = true
        };

        IdentityResult result = await UserManager.CreateAsync(user, model.Password);

        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }

        return Ok("User Registered Successfully!");
    }

    // POST api/Account/RegisterExternal
    [OverrideAuthentication]
    //[AllowAnonymous]
    [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
    [Route("RegisterExternal")]
    public async Task<IHttpActionResult> RegisterExternal()
    {
        var info = await Authentication.GetExternalLoginInfoAsync();
        if (info == null)
        {
            return InternalServerError();
        }

        var user = new ApplicationUser() { UserName = info.Email, Email = info.Email };

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

        result = await UserManager.AddLoginAsync(user.Id, info.Login);
        if (!result.Succeeded)
        {
            return GetErrorResult(result);
        }
        return Ok();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && _userManager != null)
        {
            _userManager.Dispose();
            _userManager = null;
        }

        base.Dispose(disposing);
    }

    #region Helpers

    private IAuthenticationManager Authentication
    {
        get { return Request.GetOwinContext().Authentication; }
    }

    private IHttpActionResult GetErrorResult(IdentityResult result)
    {
        if (result == null)
        {
            return InternalServerError();
        }

        if (!result.Succeeded)
        {
            if (result.Errors != null)
            {
                foreach (string error in result.Errors)
                {
                    ModelState.AddModelError("", error);
                }
            }

            if (ModelState.IsValid)
            {
                // No ModelState errors are available to send, so just return an empty BadRequest.
                return BadRequest();
            }

            return BadRequest(ModelState);
        }

        return null;
    }

    private class ExternalLoginData
    {
        public string LoginProvider { get; set; }
        public string ProviderKey { get; set; }
        public string UserName { get; set; }

        public IList<Claim> GetClaims()
        {
            IList<Claim> claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));

            if (UserName != null)
            {
                claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
            }

            return claims;
        }

        public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
        {
            if (identity == null)
            {
                return null;
            }

            Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);

            if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
                || String.IsNullOrEmpty(providerKeyClaim.Value))
            {
                return null;
            }

            if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
            {
                return null;
            }

            return new ExternalLoginData
            {
                LoginProvider = providerKeyClaim.Issuer,
                ProviderKey = providerKeyClaim.Value,
                UserName = identity.FindFirstValue(ClaimTypes.Name)
            };
        }
    }

    private static class RandomOAuthStateGenerator
    {
        private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();

        public static string Generate(int strengthInBits)
        {
            const int bitsPerByte = 8;

            if (strengthInBits % bitsPerByte != 0)
            {
                throw new ArgumentException("strengthInBits must be evenly divisible by 8.", "strengthInBits");
            }

            int strengthInBytes = strengthInBits / bitsPerByte;

            byte[] data = new byte[strengthInBytes];
            _random.GetBytes(data);
            return HttpServerUtility.UrlTokenEncode(data);
        }
    }

    #endregion
}

Here's the ConfigureAuth method in StartUp.Auth.cs file

        public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            // In production mode set AllowInsecureHttp = false
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);

        // Uncomment the following lines to enable logging in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //    consumerKey: "",
        //    consumerSecret: "");
        var facebookoptions = new FacebookAuthenticationOptions()
        {
            AppId = "",
            AppSecret = "",
            BackchannelHttpHandler = new FaceBookBackChannelHandler(),
            UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=email,first_name,last_name,gender"
        };
        facebookoptions.Scope.Add("email");
        app.UseFacebookAuthentication(facebookoptions);

        //app.UseFacebookAuthentication(
        //    appId: "",
        //    appSecret: "");

        app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
        {
            ClientId = "",
            ClientSecret = ""
        });
    }

Also here's the jQuery methods that call the API endpoints

    function getAccessToken() {
if (location.hash) {
    if (location.hash.split('access_token=')) {
        var accessToken = location.hash.split('access_token=')[1].split('&')[0];
        if (accessToken) {
            isUserRegistered(accessToken);
        }
    }
}

}

     function isUserRegistered(accessToken) {
$.ajax({
    url: 'http://localhostXXXX/api/Account/UserInfo',
    method: 'GET',
    cors: 
        {
            headers: {
                'content-type': 'application/JSON',
                'Authorization': 'Bearer ' + accessToken
            }
        },
    success: function (response) {
        if (response.HasRegistered) {
            localStorage.setItem('accessToken', accessToken);
            localStorage.setItem('userName', response.Email);
            window.location.href = "Data.aspx";
        }
        else {
            signupExternalUser(accessToken);
        }
    }
});

}

     function signupExternalUser(accessToken) {
$.ajax({
    url: 'http://localhost:XXXXX/api/Account/RegisterExternal',
    method: 'POST',
    cors: 
      {
          headers: {
              'content-type': 'application/json',
              'Authorization': 'Bearer ' + accessToken
          }
      },
    success: function () {
        window.location.href = "";
    }
});

}

1

1 Answers

1
votes

Here is the code which i used for getting my informations. its work fine:

        // check if authorized
        if (Request["code"] == null)
        {
            Response.Redirect(string.Format(
                "https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope={2}",
                app_id, Request.Url.AbsoluteUri, scope));
        }
        else
        {
            FacebookAccessToken token = new FacebookAccessToken();
            FacebookUser user = new FacebookUser();

            //Requesting for access token
            string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&scope={2}&code={3}&client_secret={4}",
                app_id, Request.Url.AbsoluteUri, scope, Request["code"].ToString(), app_secret);

            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;

            using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
            {
                StreamReader reader = new StreamReader(response.GetResponseStream());

                string vals = reader.ReadToEnd();
                // Deserialize json object
                token = JsonConvert.DeserializeObject<FacebookAccessToken>(data);
            }

            //Getting user info
            url = string.Format("https://graph.facebook.com/v2.8/me?access_token={0}", token.AccessToken);
            request = WebRequest.Create(url) as HttpWebRequest;
            using (var client = request.GetResponse() as HttpWebResponse)
            {
                StreamReader reader = new StreamReader(client.GetResponseStream());
                string data = reader.ReadToEnd();
                // data:"{\"name\":\"Er Vatsal D Patel\",\"id\":\"13168723650*****\"}";
                user = JsonConvert.DeserializeObject<FacebookUser>(data);

            }

        return "done";
    }