Continuing from part 1, I have done some refactoring. Instead of decoding the token in the frontend and then sending all the information (such as email, displayName, and userName) over HTTPS, I now prefer to send just the token. In the backend, this token is decoded and mapped to the required parameters. This approach ensures that all security aspects are handled by our backend. HTTPS provides secure transit mechanisms, so I did not consider encrypting the token. Finally, I removed the jwt-decode package from the React app.
If you are following from my previous post, the handleGoogleSuccess method would look something like this now in front end React.
const handleGoogleSuccess = (response: any) => {
const tokenId = response.credential;
dispatch(signInWithGoogle({ tokenId}))
.unwrap()
.then(() => {
toast.success('Registration successful!');
navigate('/Book');
})
.catch((error: any) => {
toast.error('Some error occurred - Please try again later');
console.error('Google sign in failed:', error);
});
console.log('Google sign in successful:', response);
}
And the controller action method would look like this.
[AllowAnonymous]
[HttpPost("registergoogle")]
public async Task<ActionResult<UserDto>> RegisterGoogle(GoogleRegisterDto googleRegisterDto)
{
GoogleJsonWebSignature.Payload payload;
try
{
payload = await GoogleJsonWebSignature.ValidateAsync(googleRegisterDto.GoogleTokenId);
}
catch (Exception ex)
{
return Unauthorized(new { Error = "Invalid google token" });
}
var email = payload.Email;
var displayName = payload.Name;
var userName = payload.Subject;
var existingUser = await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == userName || x.Email == email || x.Us_DisplayName == displayName);
if(existingUser != null)
{
if(existingUser.Email == email)
{
return CreateUserObject(existingUser);
}else
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}
}
var user = new AppUser
{
Us_DisplayName = displayName,
Email = email,
UserName = userName,
Us_Active = true,
Us_Customer = true,
Us_SubscriptionDays = 0
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest(result.Errors);
}
Only the changes from the previous method is this line var email = payload.Email;
var displayName = payload.Name;
var userName = payload.Subject;
Now, i am directly mapping the required params to save in db. This smoothen the process of login instead of checking as in the previous method here if (payload.Email != googleRegisterDto.Email)
{
return Unauthorized(new { Error = "Email doesnt match google token" });
}
And accordingly my GoogleRegisterDto now is
public class GoogleRegisterDto
{
[Required]
public string GoogleTokenId { get; set; }
}
Now, we need to add these lines in our Program.cs in order to avoid cross origin opener policy error and cross response embedder policy error.
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin");
context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp");
await next();
});
By incorporating COOP and COEP headers, we can enhance security of our app by preventing potential cross origin attack. These headers help isolate our document context and ensure only trusted resources are embedded, reducing the risk of data leaks and malicious code execution.
More can be read here.
Ref1- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy
There are many aspects of improving upon security aspect and the action should be proactive. Further improvement on this is implementation of refresh token and only allowing known ip's.
But, this is basic set up to get an idea of the flow and get us started. Thanks for your time again!
Top comments (0)