Search code examples
.netfacebookasp.net-corefacebook-authenticationios12

Authentification problem with facebook on ios 12 devices


Started facing issue with iphone devices with version 12, facebook authentification stoped work, and I can't find any reasons why this happening, the issue when you click on button facebook auth, safari just redirects to facebook and immideatly back to application.

From the code, it triggered HttpPost method ExternalLogin and then back to page with HttpGet Login. It works with other devices but only don't working on iphones version 12. Does anybody faced this issue? Also, want to note that facebook api version is 2.7

Here is my code:

<div class="form-group">
@{
    if (ViewBag.Shemas != null)
    {
        <form asp-controller="Account" asp-action="ExternalLogin" method="post" class="form-horizontal">
            <input id="ExtReturnUrl" data-val="true" name="ExtReturnUrl" asp-for="ExtReturnUrl" type="hidden" value="@Context.Request.Query["ReturnUrl"].ToString()">
            <div>
                <p>
                    @foreach (var provider in ViewBag.Shemas)
                    {
                        if (provider.DisplayName.Equals("Google"))
                        {
                            <button type="submit" class="btn btn-block text-uppercase btn-login-social  btn-login-google" id="Google" name="provider"
                                    value="Google" title="Log in using your Google account">
                                <i class="fa fa-google-plus-square"></i>Sign in via Google
                            </button>
                        }
                        else
                        {
                            <button type="submit" class="btn btn-block text-uppercase btn-login-social  btn-login-facebook" id="Facebook" name="provider"
                                    value="Facebook" title="Log in using your Facebook account">
                                <i class="fa fa-facebook-square"></i>Sign in via Facebook
                            </button>
                        }
                    }
                </p>
            </div>
        </form>
    }
}
@if (ViewData["Error"] != null)
{
    <span class="field-validation-error">@ViewData["Error"]</span>
}

And from controller:

    [HttpGet("/account/login")]
    [AllowAnonymous]
    public async Task<IActionResult> Login(string error = "", string returnUrl = "", string errorStateKey = null, string errorStateMessage = null)
    {
        if (!string.IsNullOrEmpty(returnUrl))
        {
            Request.QueryString.Add("ReturnUrl", returnUrl);
        }
        if (HttpContext.User.Identity.IsAuthenticated)
        {
            if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Home");

            // Check if ReturnUrl is Wp Support Site Url>>
            var isAbsoluteReturnUrl = IsAbsoluteValidUrl(returnUrl);
            if (!isAbsoluteReturnUrl) return RedirectToAction("Index", "Home");

            var uriWpReturn = new Uri(returnUrl);
            var hostWpReturn = uriWpReturn.Host;

            var uriWp = new Uri(_wpOptions.Value.Host);
            var hostWp = uriWp.Host;
            if (hostWpReturn == hostWp)
            {
                returnUrl = string.Format(_wpOptions.Value.AutologinUrl, CurrentSalesPeople.Var1, returnUrl);
                return Redirect(returnUrl);
            }
            // << check if ReturnUrl is Wp Support Site


            return RedirectToAction("Index", "Home");
        }

        if (!string.IsNullOrEmpty(error))
        {
            ViewData["Error"] = error;
        }
        if (errorStateKey != null && errorStateMessage != null)
        {
            ModelState.AddModelError(errorStateKey, errorStateMessage);
        }
        return await ShowLoginViewAsync(null);
    }

    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login(LoginModel model)
    {
        if (!ModelState.IsValid) return await ShowLoginViewAsync(model);

        var validatePeople = await _authService.ValidateSalespeople(model.Login, model.Password);
        if (!validatePeople.Success)
        {
            var errorStateKey = "Password";
            var errorStateMessage = validatePeople.Message;
            if (!string.IsNullOrEmpty(model.ReturnUrl))
            {
                return RedirectToAction("Login", new { returnUrl = model.ReturnUrl, errorStateKey = errorStateKey, errorStateMessage = errorStateMessage });
            }
            ModelState.AddModelError(errorStateKey, errorStateMessage);
            return await ShowLoginViewAsync(model);
        }
        var salespeopleRole = await _roleService.GetRoleNameBySalespeopleId(validatePeople.Model.SId);
        if (!salespeopleRole.Success)
        {
            var errorStateKey = "";
            var errorStateMessage = validatePeople.Message;
            if (!string.IsNullOrEmpty(model.ReturnUrl))
            {
                return RedirectToAction("Login", new { returnUrl = model.ReturnUrl, errorStateKey = errorStateKey, errorStateMessage = errorStateMessage });
            }
            ModelState.AddModelError(errorStateKey, errorStateMessage);
            return await ShowLoginViewAsync(model);
        }
        var requestForDealerToolkit = await _dealerService.GetToolkitTypeBySalespeopleIddAsync(validatePeople.Model.SId);
        if (!requestForDealerToolkit.Success)
        {
            var errorStateKey = "";
            var errorStateMessage = "Problem with your toolkit subscription. Please tell a support about this error";
            if (!string.IsNullOrEmpty(model.ReturnUrl))
            {
                return RedirectToAction("Login", new { returnUrl = model.ReturnUrl, errorStateKey = errorStateKey, errorStateMessage = errorStateMessage });
            }
            ModelState.AddModelError(errorStateKey, errorStateMessage);
            return await ShowLoginViewAsync(model);
        }
        var getToolkit = GetToolkitTypeForSalespeople(validatePeople.Model.SubscriptionTypeId, requestForDealerToolkit.Model);

        var roleName = new RoleHelper(getToolkit, salespeopleRole.Model == "Manager").RoleName;
        if (roleName == "None")
        {
            var errorStateKey = "Password";
            var errorStateMessage = "You don't have permissions";
            if (!string.IsNullOrEmpty(model.ReturnUrl))
            {
                return RedirectToAction("Login", new { returnUrl = model.ReturnUrl, errorStateKey = errorStateKey, errorStateMessage = errorStateMessage });
            }
            ModelState.AddModelError(errorStateKey, errorStateMessage);
            return await ShowLoginViewAsync(model);
        }

        //We clear session before each login to prevent caching changes in new app vesrions 
        //HttpContext.Session.Clear();

        await Authenticate(validatePeople.Model, roleName, getToolkit);


        return Redirect(model.ReturnUrl);
    }

    private async Task<IActionResult> ShowLoginViewAsync(object model)
    {
        var schemeProvider = HttpContext.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
        var shemas = await schemeProvider.GetAllSchemesAsync().FromAsyncResult(z => z.Where(x => x.Name.Equals("Facebook") || x.Name.Equals("Google")).ToList());
        ViewBag.Shemas = shemas;
        return View(model);
    }


    [HttpPost]
    [AllowAnonymous]
    public IActionResult ExternalLogin(string provider, string extReturnUrl = null)
    {
        if (User.Identity.IsAuthenticated)
        {
            return RedirectToAction("Index", "Home");
        }
        // Request a redirect to the external login provider.
        var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = extReturnUrl });
        var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
        return Challenge(properties, provider);
    }


    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
    {
        await HttpContext.SignOutAsync("Cookies");
        var externalModel = ExternalLoginModel.FromIdentity(HttpContext);

        if (externalModel != null)
        {
            var user = default(Salespeople);
            if (externalModel.Provider.Equals("Google"))
            {
                var request = await _salespeopleGetService.GetSalespeopleByPredicateAsNoTrackingAsync(p => p.GoogleKey.Equals(externalModel.Id));
                if (!request.Success)
                    return RedirectToAction("Login", new { error = "You do not have a connected google account", returnUrl = returnUrl });
                user = request.Model;
            }
            else if (externalModel.Provider.Equals("Facebook"))
            {
                var request = await _salespeopleGetService.GetSalespeopleByPredicateAsNoTrackingAsync(p => p.FacebookKey.Equals(externalModel.Id));
                if (!request.Success)
                    return RedirectToAction("Login", new { error = "You do not have a connected facebook account", returnUrl = returnUrl });
                user = request.Model;
            }
            return await Login(new LoginModel() { Login = user?.Email, Password = user?.Pass, ReturnUrl = returnUrl });
        }
        return RedirectToAction(nameof(Login));
    }



    private async Task Authenticate(Salespeople user, string role, int toolkitType)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.SId.ToString(), ClaimValueTypes.String),
            new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email),
            new Claim(ClaimsIdentity.DefaultRoleClaimType, role),
            new Claim(ClaimTypes.UserData,toolkitType.ToString()),
            new Claim(ClaimTypes.Expiration, "")
        };
        ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType,
            ClaimsIdentity.DefaultRoleClaimType);
        _cpl = new ClaimsPrincipal(id);

        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, _cpl, (await _authService.GetAuthenticationPropertiesWithPermissions(user)).Model);
    }

Solution

  • In iOS 12 Apple in their infinite wisdom chose to make safari not behave like every other browser. As a result if you follow best security practices in ASP.NET Core it breaks social auth in safari.

    There is an announcement about it here with a workaround shown below:

    services.ConfigureExternalCookie(options =>
    {
        // Other options
        options.Cookie.SameSite = SameSiteMode.None;
    });
    services.ConfigureApplicationCookie(options =>
    {
        // Other options
        options.Cookie.SameSite = SameSiteMode.None;
    });