google authentication stuck when redirecting back to my website - asp.net-core

so i use google authentication to validate the users on our system
here is my code
public IActionResult Login()
{
System.IO.File.WriteAllText("log.txt", Url.Action("ExternalLoginCallback"));
return new ChallengeResult(
GoogleDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = Url.Action("ExternalLoginCallback")
});
}
public IActionResult ExternalLoginCallback()
{
System.IO.File.AppendAllText("log.txt", "I am redirected");
var authenticateResult = HttpContext.AuthenticateAsync("External").Result;
if (!authenticateResult.Succeeded)
return BadRequest(); // TODO: Handle this better.
var claimsIdentity = new ClaimsIdentity("Application");
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));
var identity = authenticateResult.Principal.Identities.FirstOrDefault();
if (identity != null)
{
var emailClaim = identity.Claims.FirstOrDefault(c => c.Type.ToLower().Contains("emailaddress"));
if (emailClaim == null)
{
return BadRequest();
}
var emailData = emailClaim.Value;
if (string.IsNullOrEmpty(emailData))
{
return BadRequest();
}
var connectionString = _config.GetConnectionString("DefaultConnection");
UserBL userBL = new UserBL(connectionString);
var userModel = userBL.GetUserData(emailData);
if(userModel == null || userModel.HasError)
{
return BadRequest();
}
HttpContext.Session.SetString("UserData", JsonConvert.SerializeObject(userModel.Data));
if (userModel.Data.UserRoles.Any(r => r.Id == (int)UserRolesEnum.ProductOwner
|| r.Id == (int)UserRolesEnum.CopyEditor))
{
return RedirectToAction("Index", "Home");
}
else
{
return RedirectToAction("List", "Translation");
}
return RedirectToAction("UnAuthorized");
}
return BadRequest();
}
it worked fine users were redirected to google to enter their credentials and after that google redirects them back to the website till couple of days back google stopped redirecting back to the website and stays stuck on https://accounts.google.com.eg/accounts/SetSID
any pointers to how i can debug this or how to solve it
any help would be appreciated

Finally after a week i found the problem, it was not in the code it was the server.
The IIS limits the query string character count (i think the default is 260 character) and in my case google responds with a query string with 544 character in which the IIS does not accept or respond to so it stays stuck on the SetSID page
Here is where i found the solution to increase the query string max length
Increase max url length in asp.net core
as of why it was working and then stopped this i could not figure it out

Related

how to include the role to the JWT token returning?

What I have in my mind when navigating through the application, I want to save the token to the localhost along with role name and I will check if the users have access to a certain link. Is that how it works? with Authgard in Angular 8?. Can you give me some insight of navigating an application with the role from Identity(which is built in from ASP.net core 3.1).
login
// POST api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
//return null;
return BadRequest(Error.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
Generate Token Method
public static async Task<string> GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory, string userName, JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings)
{
var response = new
{
id = identity.Claims.Single(c => c.Type == "id").Value,
//probably here I want to send the role too!!
auth_token = await jwtFactory.GenerateEncodedToken(userName, identity),
expires_in = (int)jwtOptions.ValidFor.TotalSeconds
};
return JsonConvert.SerializeObject(response, serializerSettings);
}
}
You need to add claims information when generating your JWT.
Here`s an example
And another one:
1 part(how to implement JWT), 2 part(about claims here)

.net core authentication hide password from developer tools

I am using Microsoft.AspNetCore.Identity to implement basic system Login/Logout.
Here is a part of my code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM login, string returnUrl=null)
{
if (!ModelState.IsValid)
return View(login);
var foundUser = await _userManager.FindByEmailAsync(login.EmailAddress);
if (foundUser != null)
{
if (!await _userManager.IsEmailConfirmedAsync(foundUser))
{
ViewBag.Message = "Your credentials are wrong or your account is not activated.";
return View(login);
}
}
else return RedirectToAction("Register", "Account");
var result = await _signinManager.PasswordSignInAsync(
login.EmailAddress,
login.Password,
login.RememberMe,
false);
if (!result.Succeeded)
{
ModelState.AddModelError("Login error - please try again or contact support team.", "");
return View(login);
}
if (string.IsNullOrWhiteSpace(returnUrl))
return RedirectToAction("Index", "Home");
return Redirect(returnUrl);
}
It works pretty good although, I can see my passwords on Headers through Network tab(Google Chrome developer tools).
Is there a way to avoid that? Can those credentials been hidden somehow?
Any help is welcome
When submitting a form, the input data will be visible to the user entering it. However, it will be encrypted in transit if you use https.
Technichally you can use javascript to obfuscate it, but it will not increase security.

using ExternalLoginCallback for authentication how can I get UserId and UserName ASP.NET MVC5

I am doing a very basic Login in ASP.NET MVC5 using the following Action:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
string mid = ExtractMid(returnUrl);
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
if (mid == null)
{
return RedirectToAction("Login");
}
else
{
return RedirectToAction("MobileLoginFailure");
}
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
if (mid != null)
{
return redirectMobile(mid, User.Identity.GetUserName(), User.Identity.GetUserId<int>());
}
else
{
return RedirectToLocal(returnUrl);
}
case SignInStatus.Failure:
default:
return View("ExternalLoginConfirmation");
}
}
I'd expect that after SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); the user will be logged-in so that by: redirectMobile(mid, User.Identity.GetUserName(), User.Identity.GetUserId<int>()); I'll be able to get the User's Id and the user's name.
But this is not the case! User.Identity.GetUserName() returns null and User.Identity.GetUserId<int>() returns 0.
What am I doing wrong?
I can't say that I know what's going on. However, I did find some solution for my needs.
I added this:
var user = await UserManager.FindByEmailAsync(loginInfo.Email);
and used the information I needed from there. For completeness -
case SignInStatus.Success:
if (mid != null)
{
var user = await UserManager.FindByEmailAsync(loginInfo.Email);
if (user == null)
{
return RedirectToAction("MobileLoginFailure");
}
else
{
return redirectMobile(mid, user.UserName, user.Id);
}
}
else
{
return RedirectToLocal(returnUrl);
}

MVC 4 application does not go to default page after login

I have created an MVC 4 application which targets .NET 4.0. After deploying to my production server, it will show the login page but will not redirect to the default page. However, when I add debugging, I can see that the authentication process works but then the error I am getting is an error that says it can't find my View for my Error Page and then shows my Error Page. It just seems that it will not go to my "Home/Index" page - even when I remove the authorize attribute. Of course the application works in development. Additionally, it will not go to my register page or forgot login page.
My Login Controller looks like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiforgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if(ModelStat.IsValid && _userService.Login(model.UserId, model.Password))
{
var user = _userService.GetUser(model.UserId);
var loggedInUser = new LoggedInUser
{
// Build the user for custom IPrincipal
};
var userData = JsonConvert.SerializeObject(loggedInUser);
var compressData = StringCompression.Compress(userData);
var authTicket = new FormsAuthenticationTicket(
1,
user.UserId,
DateTime.Now,
DateTime.Now.AddHours(1),
false,
compressData);
var encTicket = FormsAuthentication.Encrypt(authTicket);
if(encTicket != null)
{
var faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
{
HttpOnly = true
};
Response.Cookies.Add(faCookie);
}
user.LastActivityDate = DateTime.Now;
user.LastLoginDate = DateTime.Now;
_userService.UpdateUser(user);
_uow.Commit();
return Url.IsLocalUrl(returnUrl) ? (ActionResult)Redirect(returnUrl) : RedirectToAction("Index", "Home");
}
return View(model);
and in my Global.asax:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
var decompressedData = StringCompression.Decompress(authTicket.UserData);
var loggedInUser = JsonConvert.DesrializeObject<LoggedInUser>(decompressedData);
var currrentUser = new CustomPrincipal(authTicket.Name)
{
// Build the CustomPrincipal from the loggedInUser
};
if(HttpContext.Current.User.Identity.IsAuthenticated)
{
HttpContext.Current.User = currentUser;
}
}
}
I hope that this is enough to give someone an idea of what I may be doing wrong. Somehow I feel that it is something small that I am missing. Thanks in advance.
~InDireStraits
Update:
After more troubleshooting, it would seem that the issue may have something to do with the fact that I am using a BaseController for specifying permissions but I am still baffled as to why the application works as intended in my development environment but not in production. To verify my IIS settings I installed the default MVC4 App to production which does not have .NET 4.5, and it runs. I am using VS 2012 so I do have 4.5. Could I somehow be introducing .NET 4.5 classes or functionality even if this targets .NET 4.0? At any rate, here is my BaseController code:
public class BaseController: Controller
{
private string _actionKey;
private const string PermisisionList = "permissionList";
private Dictionary<string, string> _requiredActionPermissions;
private static readonly IControllerActionService<ControllerAction> _actionService;
protected new CustomPrincipal User
{
get
{
return HttpContext.User as CustomPrincipal;
}
}
public BaseController(IControllerActionService<ControllerAction> actionService)
{
_actionService = actionService;
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check to see if the PermissionList is loaded and load if necessary
if(!CacheLayer.Exists(PermissionList))
{
_requiredActionPermissions = _actionService.GetControllerActionDictionary();
CacheLayer.Add(_requiredActionPermissions, PermissionList);
}
else
{
_requiredActionPermission = CacheLayer.Get<Dictionary<string, string>>(PermissionList);
}
// Get the Controller/Action of the current request
_actionKey = string.Format("{0}-{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName);
// If the user is authenticated, grab the permissions
if(filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var userPermissions = User.Permissions;
if(!_requiredActionPermissions.Values.Any(a=>a.Equals(_actionKey, StringComparison.OrdinalIgnoreCase)))
{
return;
}
if(userPermissions.Contains(_requiredActionsPermissions.FirstOrDefault(x=>x.Value == _actionKey).Key))
{
return;
}
filterContext.Result = new RedirectResult("~/Error/ErrorUnauthorized");
return;
}
if(!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if(!_requiredActionPermissions.Values.Any(a=>a.Equals(_actionKey, StringComparison.OrdinalIgnoreCase)))
{
return;
}
}
if(filterContext.HttpContext.Request.Url == null)
{
return;
}
if(filterContext.HttpContext.Request.Url.AbsolutePath == FormsAuthentication.LoginUrl)
{
return;
}
var redirectUrl = string.Format("?returnUrl={0}", filterContext.HttpContext.Request.Url.PathAndQuery);
filterContext.HttpContext.Response.Redirect(FormsAuthentication.LoginUrl + redirectUrl, true);
}
UPDATE 2: Installed .NET 4.52 on Staging Server and the application now works as intended. The problem is that I will not be able to install this on the production server. I don't understand what it is that 4.5 is fixing that 4.0 does not seem to facilitate. HELLLLLLPPP!!!!!!
The answer can be found here. To summarize, I added an extra parameter to my route config that worked in 4.5 but not in 4.0. Will follow up on the linked question. Thanks

return login success from a web api to an mvc 4 application

my colleague and myself are working on an application form with login functionality the user logs in from the mvc 4 app and there details are submitted to the web api to be checked against the values held in the database once verified the web api returns a loginResult class that contains the error message (if any) and a bool for stating whether it has been successful or not.
at the mvc 4 application level the code below is used to submit the login details to the web api:
Login Action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountViewModel model)
{
if (!ModelState.IsValid) return View("Login", model);
await _client.PostAsJsonAsync("api/Applicant/CheckApplicant", model)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
var service = DependencyResolver.Current.GetService<IApplyService>();
var loginResult = service.GetLoginResult();
var loginSuccess = loginResult.LoginSuccess;
if (loginSuccess != null && (bool) loginSuccess)
{
FormsAuthentication.SetAuthCookie(model.Email, model.RememberMe);
return RedirectToRoute("Terms And Conditions");
}
return View("Login");
}
the login details are then received at the web api in this method:
Check Applicant Method
public String CheckApplicant(Applicant applicant)
{
Int32 passwordFailureTimeoutMins = Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["PasswordFailureTimeoutMins"]);
Int32 passwordFailureAttempts = Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["PasswordFailureAttempts"]);
ApplicantRepository applicantRepository = new ApplicantRepository();
Applicant applicantDB = applicantRepository.GetById(applicant.Email);
LoginResult loginResult = new LoginResult();
PasswordHelper passwordHelper = new PasswordHelper();
if (applicantDB == null)
{
loginResult.LoginSuccess = false;
loginResult.LoginError = "Your password or login may not be correct.";
}
else
{
bool loginFailureCheck;
if (applicantDB.LoginFailureCount > passwordFailureAttempts)
{
System.TimeSpan diffResult = DateTime.Now.Subtract(Convert.ToDateTime(applicantDB.LastLoginFailure));
if (diffResult.Minutes < passwordFailureTimeoutMins)
{
loginFailureCheck = false;
}
else
{
loginFailureCheck = true;
}
}
else
{
loginFailureCheck = true;
}
if (passwordHelper.CheckPassword(applicant.Password, applicantDB.Password))
{
if(loginFailureCheck)
{
if(applicantDB.AccountActive)
{
loginResult.LoginSuccess = true;
loginResult.LoginError = "Login Successful.";
applicantDB.LastLoginFailure = null;
applicantDB.LastLoginSuccess = DateTime.Now;
applicantDB.LoginFailureCount = 0;
applicantRepository.Update(applicantDB);
}
else
{
loginResult.LoginSuccess = false;
loginResult.LoginError = "This account has been permanently banned.";
}
}
else
{
loginResult.LoginSuccess = false;
loginResult.LoginError = "This account is now temporarily disabled please wait " + passwordFailureTimeoutMins + " minutes before trying again";
applicantDB.LastLoginFailure = DateTime.Now;
applicantDB.LoginFailureCount = applicantDB.LoginFailureCount + 1;
applicantRepository.Update(applicantDB);
}
}
else
{
loginResult.LoginSuccess = false;
loginResult.LoginError = "Your password or login may not be correct.";
applicantDB.LastLoginFailure = DateTime.Now;
applicantDB.LoginFailureCount = applicantDB.LoginFailureCount + 1;
applicantRepository.Update(applicantDB);
}
}
return JsonConvert.SerializeObject(loginResult);
}
as you can see it returns a JsonConvert.SerializeObject(loginResult).
when this is done the process returns to the Login ActionResult as above it then moves to the GetLoginResult() method as shown below:
GetLoginResult
public LoginResult GetLoginResult()
{
const string uri = "http://localhost:55830/api/Applicant/CheckApplicant";
using (var httpClient = new HttpClient())
{
var response = httpClient.GetStringAsync(uri);
return JsonConvert.DeserializeObject<LoginResult>(response.Result);
}
}
when it get to this point it returns an error 405 method not allowed.
How do I consume the loginResult at the mvc 4 app level and what is the best way of sending the loginResult from the web api?
Any advice would be greatly appreciated.
Not sure what exactly you are trying to do but are you making a GET to read the result of the previous POST? You can read the response message of POST to get the result, like this.
public async Task<ActionResult> Login(AccountViewModel model)
{
if (!ModelState.IsValid) return View("Login", model);
var message = await _client.PostAsJsonAsync
("api/Applicant/CheckApplicant", model);
message.EnsureSuccessStatusCode();
LoginResult result = await message.Content.ReadAsAsync<LoginResult>();
// do other stuff here
}
Change the web API action method to return LoginResult directly. The framework will serialize it for you.
public LoginResult CheckApplicant(Applicant applicant)
{
}