ASP.NET Core Web API - How to send Registration Notification Email without link and token - asp.net-core

In my ASP.NET Core-6 Web API, I am sending email notification to users when his user registration account is created.
MailService:
public class MailService : IMailService
{
private readonly MailSettings _mailSettings;
private readonly ILogger _logger;
public MailService(MailSettings mailSettings, ILogger logger)
{
_logger = logger;
_mailSettings = mailSettings;
}
public async Task<bool> SendEmailAsync(MailRequest mailRequest)
{
var email = new MimeMessage { Sender = MailboxAddress.Parse(_mailSettings.Mail) };
email.To.Add(MailboxAddress.Parse(mailRequest.ToEmail));
email.Subject = mailRequest.Subject;
var builder = new BodyBuilder();
if (mailRequest.Attachments != null)
{
foreach (var file in mailRequest.Attachments.Where(file => file.Length > 0))
{
byte[] fileBytes;
await using (var ms = new MemoryStream())
{
file.CopyTo(ms);
fileBytes = ms.ToArray();
}
builder.Attachments.Add((file.FileName + Guid.NewGuid().ToString()), fileBytes, ContentType.Parse(file.ContentType));
}
}
builder.HtmlBody = mailRequest.Body;
email.Body = builder.ToMessageBody();
try
{
using var smtp = new SmtpClient();
smtp.Connect(_mailSettings.Host, _mailSettings.Port, SecureSocketOptions.StartTls);
smtp.Authenticate(_mailSettings.Mail, _mailSettings.Password);
await smtp.SendAsync(email);
smtp.Disconnect(true);
return true;
}
catch (Exception e)
{
_logger.Error(e, e.Source, e.InnerException, e.Message, e.ToString());
return false;
}
}
}
MailRequest:
public class MailRequest
{
public string ToEmail { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public List<IFormFile> Attachments { get; set; }
}
GetEmailBody:
public static async Task<string> GetEmailBody(string emailTempPath, string token, string email)
{
var link = $"https://myapp.com/Manager/RegisterManager?email={email}&token={token}";
var temp = await File.ReadAllTextAsync(Path.Combine(Directory.GetCurrentDirectory(), emailTempPath));
var emailBody = temp.Replace("**link**", link);
return emailBody;
}
Register:
public async Task<Response<string>> Register(RegisterUserDto model)
{
var user = _mapper.Map<AppUser>(model);
user.IsActive = true;
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, UserRoles.Customer);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = TokenConverter.EncodeToken(token);
var userRole = await _userManager.GetRolesAsync(user);
var mailBody = await EmailBodyBuilder.GetEmailBody(user, userRole.ToList(), emailTempPath: "StaticFiles/Html/ConfirmEmail.html", linkName: "ConfirmEmail", encodedToken, controllerName: "Authentication");
var mailRequest = new MailRequest()
{
Subject = "Registration Notification",
Body = mailBody,
ToEmail = model.Email
};
bool emailResult = await _mailService.SendEmailAsync(mailRequest);
if (emailResult)
{
_logger.Information("Mail sent successfully");
var customer = new Customer
{
AppUser = user
};
await _unitOfWork.Customers.InsertAsync(customer);
await _unitOfWork.Save();
response.StatusCode = (int)HttpStatusCode.Created;
response.Succeeded = true;
response.Data = user.Id;
response.Message = "User created successfully! Please check your mail to verify your account.";
transaction.Complete();
return response;
}
_logger.Information("Mail service failed");
transaction.Dispose();
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Succeeded = false;
response.Message = "Registration failed. Please try again";
return response;
}
response.Message = GetErrors(result);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Succeeded = false;
transaction.Complete();
return response;
};
}
I want to send the Username and Password to the user as Email notification. This will not include link and token. And the will not confirm, but just to know his Username and password.
How do I modify the code above to achieve this?
Thanks

It seems you are using Microsoft.AspNetCore.Identity for the functionality.
You can disable email verification as you intend to not require the user functionality.
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = false;
})
And you can have your Username information from var user = _mapper.Map<AppUser>(model); and password from RegisterUserDto model, just construct the email body with these value and put it in mailBody variable.

Related

How to create a service for user login in Identity

In order to create a token in jwt, I must first check that the received user password is correct. I want to write a service for login and create its interface and use it in the JWTAuthorizationManager class or anywhere, please correct the codes.
public class JWTAuthorizationManager
{
public JwtFeilds Authenticate(string UserName, string PasswordHash)
{
//ایجاد تاریخ انقضای توکن
var tokenExpireTimeStamp = DateTime.Now.AddHours(Constansts.JWT_TOKEN_EXPIRE_TIME);
//ایجاد متغیر از کلاس مشخص شده برای ایجاد توکن و اطلاعات همراه آن
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
//ایجاد آرایه ای از بایت ها به عنوان کلید توکن
var tokenKey = Encoding.ASCII.GetBytes(Constansts.JWT_SECURITY_KEY_FOR_TOKEN);
//از این کلاس برای نگهداری ویژگیها و اطلاعات درون توکن استفاده می شود.
var securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new List<Claim>
{
new Claim("username", UserName),
new Claim(ClaimTypes.PrimaryGroupSid,"User Group 01")
}),
Expires = tokenExpireTimeStamp,
//امضا یا اعتبارنامه یا مجوز ورود
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(tokenKey),SecurityAlgorithms.HmacSha256Signature)
};
var securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
var token = jwtSecurityTokenHandler.WriteToken(securityToken);
return new JwtFeilds
{
token = token,
user_name = UserName,
expire_time = (int)tokenExpireTimeStamp.Subtract(DateTime.Now).TotalSeconds
};
}
}
public class loginService
{
private readonly SignInManager<User> _signInManager;
public loginService(SignInManager<User> signInManager)
{
_signInManager = signInManager;
}
public async Task<loginService> UserLogin(string UserName, string PasswordHash)
{
var result = await _signInManager.PasswordSignInAsync(UserName, PasswordHash, true,
lockoutOnFailure: false);
if (result.Succeeded)
{
return null;
}
return null;
}
}
interface IuserLogin
{
}
[HttpPost]
public IActionResult Login([FromForm] User model)
{
var jwtAuthorizationManager = new JWTAuthorizationManager();
var result = jwtAuthorizationManager.Authenticate(model.UserName, model.PasswordHash);
if (result == null)
return Unauthorized();
else
return Ok(result);
}
The token creation is done successfully, but I want the user to be identified before creating the token
The easiest way, you can choose to return a boolean Task:
public interface ILoginService
{
//If your model has other attributes, pass them in together
Task<bool> UserLogin(string UserName, string PasswordHash);
}
public class Login : ILogin
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
public Login(SignInManager<IdentityUser> signInManager,UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public async Task<bool> UserLogin(string UserName, string PasswordHash)
{
var user = await _userManager.FindByEmailAsync(UserName);
if (user != null && !user.EmailConfirmed)
{
return false;
}
if (await _userManager.CheckPasswordAsync(user, PasswordHash) == false)
{
return false;
}
var result = await _signInManager.PasswordSignInAsync(UserName, PasswordHash, true);
if (result.Succeeded)
{
return true;
}
else
{
return false;
}
}
}
In the Login Action:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(UserLoginDto model)
{
if (ModelState.IsValid)
{
//Call the ILoginService interface
var result = _loginService.UserLogin(model.Email,model.Password);
if (result.Result)
{
var jwtAuthorizationManager = new JWTAuthorizationManager();
var result = jwtAuthorizationManager.Authenticate(model.UserName, model.PasswordHash);
//do something
}
else
{
ModelState.AddModelError("message", "Invalid login attempt");
return View(model);
}
}
return View(model);
}
Don't forget to sign up for the service:
builder.Services.AddTransient<ILoginService, LoginService>();
You can also return a string or other types of Task, up to you.
Hope this can help you.

Asp.net Core Object reference not set to an instance of an object

ASP.NET CORE API
The logged in user gives an error in the code below while adding a photo. Can anybody help?
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value)
This code gives an error. Help me
Object reference not set to an instance of an object
PhotosController.cs
[HttpPost]
public ActionResult AddPhotoForCity(int cityId,[FromForm]PhotoForCreationDto photoForCreationDto)
{
var city = _appRepository.GetCityById(cityId);
if (city == null)
{
return BadRequest("Could not find the city.");
}
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
karşılaştırmak gibi
if (currentUserId != city.UserId)
{
return Unauthorized();
}
var file = photoForCreationDto.File;
var uploadResult = new ImageUploadResult();
if (file.Length > 0)
{
using (var steam = file.OpenReadStream())
{
var uploadParams = new ImageUploadParams()
{
File = new FileDescription(file.Name,steam)
};
uploadResult = _cloudinary.Upload(uploadParams);
}
}
photoForCreationDto.Url = uploadResult.Url.ToString();
photoForCreationDto.PublicId = uploadResult.PublicId;
var photo = _mapper.Map<Photo>(photoForCreationDto);
photo.City = city;
if (!city.Photos.Any(p => p.IsMain))
{
photo.IsMain = true;
}
city.Photos.Add(photo);
if (_appRepository.SaveAll())
{
//eklenen fotoğrafı döndürüyoruz
var photoToRetun = _mapper.Map<Photo>(photoForCreationDto);
return CreatedAtRoute("GetPhoto", new {id = photo.Id}, photoToRetun);
}
return BadRequest("Cloud not add the photo");
}
AuthController.cs
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private IAuthRepository _authRepository;
private IConfiguration _configuration;
public AuthController(IAuthRepository authRepository, IConfiguration configuration)
{
_authRepository = authRepository;
_configuration = configuration;
}
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] UserForRegisterDto userForRegisterDto)
{
if (await _authRepository.UserExists(userForRegisterDto.UserName))
{
ModelState.AddModelError("UserName", "Username already exists");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userToCreate = new User
{
UserName = userForRegisterDto.UserName
};
var createdUser = await _authRepository.Register(userToCreate, userForRegisterDto.Password);
return StatusCode(201);
}
[HttpPost("login")]
public async Task<ActionResult> Login([FromBody] UserForLoginDto userForLoginDto)
{
var user = await _authRepository.Login(userForLoginDto.UserName, userForLoginDto.Password);
if (user == null)
{
return Unauthorized();
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:Token").Value);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName)
}),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key)
, SecurityAlgorithms.HmacSha512Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(tokenString);
}
}
From the above code snippet you have shared, they mostly likely reason you are getting this error is because the user is not logged in, and hence this line of code
User.FindFirst(ClaimTypes.NameIdentifier).Value
is throwing an exception.
You could either do it like this.
User.FindFirst(ClaimTypes.NameIdentifier)?.Value
Or
[HttpPost]
[Authorize] // make sure you authorize your action method by adding this attribute and
// only allow logged in user to access it.
public ActionResult AddPhotoForCity(int cityId,[FromForm]PhotoForCreationDto photoForCreationDto)
{
}
try this one:
var currentUserId = int.Parse(User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value);

Getting " InvalidOperationException: Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler`2"

I'm trying to implement refresh token in my api. The api is following CQRS+MediatR pattern and is using JWT for authentication.
After adding a Handler to manage refresh call and a refresh token service I am getting the following error:
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler`2
[UM.Business.Application.Token.Command.Update.UpdateTokenCommand,UM.Business.Application.Common.HandlerResult]
Lifetime: Transient ImplementationType: UM.Business.Application.Token.Command.Update.UpdateTokenHandler':
Unable to resolve service for type 'UM.Infrastructure.Common.Configuration.AuthSettings'
while attempting to activate 'UM.Business.Application.Token.Command.Update.UpdateTokenHandler'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'UM.Infrastructure.Common.Configuration.AuthSettings'
while attempting to activate 'UM.Business.Application.Token.Command.Update.UpdateTokenHandler'.
My service is doing basic crud operations on the Refresh Token class.
First of all I added dependancy injection in a service module class,
service.AddTransient<IRefreshTokenService, RefreshTokenService>();
This is my controller end point,
[Route("refresh")]
[ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(IEnumerable<string>), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(string), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ConfirmEmail), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(AccountBlocked), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Refresh([FromBody] UpdateRefreshTokenVM request, CancellationToken ct)
{
var refreshCommand= _mapper.Map<UpdateTokenCommand>(request);
var authenticationResult = await _mediator.Send(refreshCommand, ct);
if (authenticationResult == null)
return Unauthorized();
if (authenticationResult.IsSuccess)
return Ok(authenticationResult.Result);
}
My UpdateRefreshTokenVM,
public class UpdateRefreshTokenVM
{
public string AccessToken { get; set; }
public Core.Domain.Models.RefreshToken RefreshToken { get; set; } }
And UpdateTokenCommand,
public class UpdateTokenCommand : CommandBase<HandlerResult>
{
public string AccessToken { get; set; }
public Core.Domain.Models.RefreshToken RefreshToken { get; set; }
}
Handler getting called is this one,
public UpdateTokenHandler(IUserService userService, IMapper mapper, IRefreshTokenService refreshTokenService, IRoleService roleService, AuthSettings authSetting, IUserClaimsService userClaimService)
{
_userService = userService;
_refreshTokenService = refreshTokenService;
_userClaimService = userClaimService;
_roleService = roleService;
_authSetting = authSetting;
_mapper = mapper;
}
public async Task<HandlerResult> Handle(UpdateTokenCommand request, CancellationToken cancellationToken)
{
var loginResponse = new HandlerResult();
var userIdentity = await _userService.FindByEmailAsync(request.RefreshToken.User.Email);
string accessToken = request.AccessToken;
var refreshToken = request.RefreshToken;
var principal = TokenGenerator.GetPrincipalFromExpiredToken(accessToken);
var username = principal.Identity.Name; //this is mapped to the Name claim by default
var refreshTokenFromDb =await _refreshTokenService.FindByUserId(refreshToken.User.Id);
if (refreshTokenFromDb.RefreshTokenExpiryTime <= DateTime.Now)
{
return null;
}
if (refreshTokenFromDb == null || refreshTokenFromDb != refreshToken )
{
loginResponse.IsSuccess = false;
loginResponse.ErrorMessage="Invalid client request";
loginResponse.Result = null;
return loginResponse;
}
var userClaims = await _userClaimService.GetClaims(userIdentity);
var userRoles = await _roleService.GetUserRoles(userIdentity);
var resultObject = TokenGenerator.GenerateJsonWebToken(userIdentity, userClaims, _authSetting, userRoles);
//var newRefreshToken = TokenGenerator.GenerateRefreshToken();
loginResponse.Result = resultObject;
loginResponse.ErrorMessage = null;
loginResponse.IsSuccess = true;
//user.RefreshToken = newRefreshToken;
//userContext.SaveChanges();
// now save the token variable in db
await _refreshTokenService.UpdateAsync(resultObject.RefreshToken);
return loginResponse;
}
}
Following is the token generator used in the handler,
internal static LoginResponse GenerateJsonWebToken(UM.Core.Domain.Models.User userInfo,IList<System.Security.Claims.Claim> userClaims, AuthSettings authSetting, IList<string> roleNames)
{
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Email, userInfo.Email));
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.NameIdentifier, userInfo.Id));
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Name, userInfo.NormalizedUserName));
foreach(string name in roleNames)
{
userClaims.Add(new System.Security.Claims.Claim(ClaimTypes.Role, name));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSetting.Key));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor()
{
Issuer = authSetting.Issuer,
Audience = authSetting.Audience,
Subject = new ClaimsIdentity(userClaims),
Expires = DateTime.UtcNow.AddMinutes(authSetting.ExpirationTimeInMin),
SigningCredentials = credentials
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
// Now generate refresh token
var refreshToken = new RefreshToken
{
User = userInfo,
//UserId = int.Parse(userInfo.Id),
RefreshTokenString = GenerateRefreshToken(),
RefreshTokenExpiryTime = DateTime.Now.AddDays(14) // will change this later
};
var authenticationResponse = new LoginResponse
{
AccessToken = tokenHandler.WriteToken(token),
RefreshToken = refreshToken,
IsValid = true
};
return authenticationResponse;
}
public static string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
public static ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#345")),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
Auth settings(which I didn't touch for this implementation)
public class AuthSettings
{
public string Key { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationTimeInMin { get; set; }
}
And my login Response class,
public class LoginResponse
{
public string AccessToken { get; set; }
public RefreshToken RefreshToken { get; set; }
public bool IsValid { get; set; }
public bool VerificationRequired { get; set; }
public bool TwoFactorRequired { get; set; }
public LoginResponse()
{
RefreshToken = new RefreshToken();
}
}

Login API method returning 400 Bad request in Xamarin Forms

I have a login API method in Xamarin for which I am using a .net core project.The back end login in the web application works perfectly returning the token once I am logged in,but on the client side in Xamarin I am having troubles with it as the response returns a 400 Bad request.I checked the API and everything seems to be fine.I pass the token in the headers for the request along with the credentials and I am checking for the request.
This is the client side API:
public async Task<string> Login(string email, string password)
{
var urlLogin = "http://10.0.2.2:5000/api/Token/";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", email),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
});
var httpClient = new HttpClient();
var authData = string.Format("{0}:{1}", email, password);
var authHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(authData));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authHeaderValue);
var json = JsonConvert.SerializeObject(formContent);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
CancellationTokenSource cts = new CancellationTokenSource();
httpClient.DefaultRequestHeaders.Accept.Clear();
var responseMessage = httpClient.PostAsync(urlLogin, content).Result;
if(responseMessage.IsSuccessStatusCode)
{
await App.Current.MainPage.DisplayAlert("Login succesful!", "Welcome", "ok", "cancel");
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new UsersPage());
}
else
{
await App.Current.MainPage.DisplayAlert("Wrong credentials", "Please try again!", "ok", "cancel");
}
}
catch (Exception ex)
{
ex.Message.ToString();
throw;
}
return "";
}
The server-side login api:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Post(User userData)
{
if (userData != null && userData.Email != null && userData.Password != null)
{
var user = await GetUser(userData.Email, userData.Password);
if (user != null)
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, config["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("Id", user.Id.ToString()),
new Claim("Name", user.Name),
new Claim("Phone", user.Phone.ToString()),
new Claim("Email", user.Email),
new Claim ("ConfPassword",user.ConfPassword)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(config["Jwt:Issuer"], config["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: signIn);
return Ok(new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return BadRequest("Invalid credentials");
}
}
else
{
return BadRequest();
}
}
private async Task<User> GetUser(string email, string password)
{
return await _context.User.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
User.cs
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Password { get; set; }
public string ConfPassword { get; set; }
}
Can someone please help me figure out what is wrong with the API?I am new in working with mobile API so please bear with me.
I have managed to make it work as #Jason suggested.This is the working method on the client side:
public async Task<string> Login(User user)
{
var urlLogin = "http://10.0.2.2:5000/api/Token/";
var httpClient = new HttpClient();
var authData = string.Format("{0}:{1}", user.Email, user.Password);
var authHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(authData));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authHeaderValue);
var json = JsonConvert.SerializeObject(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
CancellationTokenSource cts = new CancellationTokenSource();
httpClient.DefaultRequestHeaders.Accept.Clear();
var responseMessage = httpClient.PostAsync(urlLogin, content).Result;
if(responseMessage.IsSuccessStatusCode)
{
await App.Current.MainPage.DisplayAlert("Login succesful!", "Welcome", "ok", "cancel");
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new UsersPage());
}
else
{
await App.Current.MainPage.DisplayAlert("Wrong credentials", "Please try again!", "ok", "cancel");
}
}
catch (Exception ex)
{
ex.Message.ToString();
throw;
}
return "";
}

Getting email from oauth authentication (Microsoft)

How can I get the email from microsoft account? I'm doing the following:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.ExtraData["????"];
}
}
For google and facebook I'm able to get the email but I can't with microsoft? What kew should I use?
Solution:
public class MicrosoftScopedClient : IAuthenticationClient
{
private string clientId;
private string clientSecret;
private string scope;
private const string baseUrl = "https://login.live.com/oauth20_authorize.srf";
private const string tokenUrl = "https://login.live.com/oauth20_token.srf";
public MicrosoftScopedClient(string clientId, string clientSecret, string scope)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
}
public string ProviderName
{
get { return "Microsoft"; }
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.ToString();
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"];
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = QueryAccessToken(redirectURI, accessCode);
if (token == null || token == "")
{
return null;
}
var userData = GetUserData(token);
return userData;
}
private IDictionary<string, string> GetUserData(string accessToken)
{
ExtendedMicrosoftClientUserData graph;
var request =
WebRequest.Create(
"https://apis.live.net/v5.0/me?access_token=" + EscapeUriDataStringRfc3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
graph = JsonConvert.DeserializeObject<ExtendedMicrosoftClientUserData>(data);
}
}
}
var userData = new Dictionary<string, string>();
userData.Add("id", graph.Id);
userData.Add("username", graph.Name);
userData.Add("name", graph.Name);
userData.Add("link", graph.Link == null ? null : graph.Link.AbsoluteUri);
userData.Add("gender", graph.Gender);
userData.Add("firstname", graph.FirstName);
userData.Add("lastname", graph.LastName);
userData.Add("email", graph.Emails.Preferred);
return userData;
}
private string QueryAccessToken(string returnUrl, string authorizationCode)
{
var entity =
CreateQueryString(
new Dictionary<string, string> {
{ "client_id", this.clientId },
{ "redirect_uri", returnUrl },
{ "client_secret", this.clientSecret},
{ "code", authorizationCode },
{ "grant_type", "authorization_code" },
});
WebRequest tokenRequest = WebRequest.Create(tokenUrl);
tokenRequest.ContentType = "application/x-www-form-urlencoded";
tokenRequest.ContentLength = entity.Length;
tokenRequest.Method = "POST";
using (Stream requestStream = tokenRequest.GetRequestStream())
{
var writer = new StreamWriter(requestStream);
writer.Write(entity);
writer.Flush();
}
HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
if (tokenResponse.StatusCode == HttpStatusCode.OK)
{
using (Stream responseStream = tokenResponse.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
var tokenData = JsonConvert.DeserializeObject<OAuth2AccessTokenData>(data);
if (tokenData != null)
{
return tokenData.AccessToken;
}
}
}
}
return null;
}
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
private static string EscapeUriDataStringRfc3986(string value)
{
StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
private static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args)
{
if (!args.Any())
{
return string.Empty;
}
StringBuilder sb = new StringBuilder(args.Count() * 10);
foreach (var p in args)
{
sb.Append(EscapeUriDataStringRfc3986(p.Key));
sb.Append('=');
sb.Append(EscapeUriDataStringRfc3986(p.Value));
sb.Append('&');
}
sb.Length--; // remove trailing &
return sb.ToString();
}
protected class ExtendedMicrosoftClientUserData
{
public string FirstName { get; set; }
public string Gender { get; set; }
public string Id { get; set; }
public string LastName { get; set; }
public Uri Link { get; set; }
public string Name { get; set; }
public Emails Emails { get; set; }
}
protected class Emails
{
public string Preferred { get; set; }
public string Account { get; set; }
public string Personal { get; set; }
public string Business { get; set; }
}
}
AuthConfig.cs
public static class AuthConfig
{
public static void RegisterAuth()
{
Dictionary<string, object> MicrosoftsocialData = new Dictionary<string, object>();
MicrosoftsocialData.Add("Icon", "../Content/icons/microsoft.png");
OAuthWebSecurity.RegisterClient(new MicrosoftScopedClient("XXXXXXXX", "YYYYYYYYYYYYY",
"wl.basic wl.emails"), "Microsoft", MicrosoftsocialData);
//......
}
}
Usage:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.UserName;
}
}
Based on: How OAuthWebSecurity to obtain emails for different oauth clients, but Microsoft Client doesn’t return email, it didn’t include scope “wl.emails”
or even simpler: https://stackoverflow.com/a/22723713/1586498
var mo =
new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions
{
CallbackPath = new Microsoft.Owin.PathString("/Callbacks/External"),//register at oAuth provider
ClientId = "<<yourclientid>>",
ClientSecret = "<<yourclientsecret>>",
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(providerKey, context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
return System.Threading.Tasks.Task.FromResult(0);
}
}
};
mo.Scope.Add("wl.basic");
mo.Scope.Add("wl.emails"); //HERE IS THE GOLD
app.UseMicrosoftAccountAuthentication(mo);
and my way of grabbing them:
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
externalIdentity.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Email));
amp's answer really helped me out.
Also want to mention that you have to check the 'Live SDK support' checkbox when you register your application (https://apps.dev.microsoft.com/) - otherwise the OAuth service complains that you don't have a client secret (even if you do).
Just wanted to add how to do this without using the AuthConfig.cs stuff in case anyone is interested (a bit more manual, but it makes it easier to understand if you're not familiar with the framework):
public ActionResult LoginWithMicrosoftAccount(CancellationToken cancellationToken)
{
var client = new MicrosoftScopedClient(appID, appsecret, "wl.basic wl.emails");
var urlNoQueryString = Request.Url.GetLeftPart(UriPartial.Path);
AuthenticationResult result = null;
if(Request.QueryString["error"]!= null)
{//Microsoft service returns error
return View();
}
if (Request.QueryString["code"] != null)
{
result = client.VerifyAuthentication(this.HttpContext);
//at this point, you should get the username from result.UserName
}
if(Request.QueryString["code"]==null || result.UserName == null)
{//will do the redirection
client.RequestAuthentication(this.HttpContext, new Uri(urlNoQueryString));
}
return View();
}