none
External Logins using ASP.NET web API RRS feed

  • Question

  • I'm developing a website which uses AngularJS in the frontend and ASP.NET web API in the backend. I'm trying to configure external logins(Facebook) for my site. I have already enabled Cors on the API. The order of API calls are:

    API call to

     api/Account/ExternalLogins?returnUrl=%2F&generateState=true


    to get a list of external login providers.
    This returns

    [{"name":"Facebook","url":"/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx","state":"xxxxxxxxxx"}]


    Now I send a GET request to URL returned earlier. This triggers a preflight request to

     https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx

    which gives error. This is never redirected to Facebook Login Page.

    Both the request and response headers for

     /api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx

    contains Access-Control-Allow-Origin:*

    But for the preflight request,

    https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx

    Access-Control-Allow-Origin header is missing.

    The code snippets are mentioned below.

    app.js

    $httpProvider.defaults.headers.common['Access-Control-Allow-Origin'] = '*';   

    $httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, Content-Length, X-Requested-With';   

    $httpProvider.defaults.headers.common['Access-Control-Allow-Credentials'] = true;   

    $httpProvider.defaults.headers.common['Access-Control-Allow-Method'] = 'GET, PUT, POST, DELETE, OPTIONS';


         


    Start.Auth.cs

                app.CreatePerOwinContext(ApplicationDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
                app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
    
                app.UseCors(CorsOptions.AllowAll);
    
                // 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
                // Configure the sign in cookie
                app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/api/Account/ExternalLogin"),
                    Provider = new CookieAuthenticationProvider
                    {
                        // Enables the application to validate the security stamp when the user logs in.
                        // This is a security feature which is used when you change a password or add an external login to your account.  
                        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                    }
                });
    
                app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
                // 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
                    //Provider = new AuthorizationServerProvider()
                };
    
                // Enable the application to use bearer tokens to authenticate users
                app.UseOAuthBearerTokens(OAuthOptions);
    
                // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
                app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
    
                // Enables the application to remember the second login verification factor such as phone or email.
                // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
                // This is similar to the RememberMe option when you log in.
                app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    
                
    
                var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
                {
                    AppId = "xxxxxxxxx",
                    AppSecret = "xxxxxxxxx",
                    BackchannelHttpHandler = new FacebookBackChannelHandler(),
    
                    Provider = new FacebookAuthenticationProvider()
                    {
                        OnAuthenticated = (context) =>
                        {
                            context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
                            return Task.FromResult(0);
                        }
    
                    },
                    SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
                    SendAppSecretProof = true
                };
    
                facebookOptions.Scope.Add(ConfigurationManager.AppSettings["Facebook_Scope"]);
                facebookOptions.AuthenticationMode = AuthenticationMode.Passive;
                app.UseFacebookAuthentication(facebookOptions);


    AccountController

    [Authorize]
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/Account")]
    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)]
        [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")]
        [AllowAnonymous]
        public IHttpActionResult Logout()
        {
            Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
            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();
        }
    
        // GET api/Account/ExternalLogin
        [OverrideAuthentication]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
        [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 };
    
            IdentityResult result = await UserManager.CreateAsync(user, model.Password);
    
            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }
    
            return Ok();
        }
    
        // POST api/Account/RegisterExternal
        [OverrideAuthentication]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("RegisterExternal")]
        public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
    
            var info = await Authentication.GetExternalLoginInfoAsync();
            if (info == null)
            {
                return InternalServerError();
            }
    
            var user = new ApplicationUser() { UserName = model.Email, Email = model.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);
        }
    
    }


    I know this is achievable using MVC Account Controller. I'm looking for a way to do this entirely using web API with no dependency on MVC.

    Thanks!



    Sunday, June 11, 2017 10:51 AM

All replies