EF Core LogIn Taking a Long Time - sql-server

I'm trying to make an endpoint for logging in with a token in ASP.NET Core. The API is using EFCore, but my code is taking a long time to log me in.
This is the service:
public async Task<AuthModel> GetTokenAsync(UserLoginDto model)
{
var authModel = new AuthModel();
var user = await _userManager.FindByEmailAsync(model.Email);
if (user is null || !await _userManager.CheckPasswordAsync(user, model.Password))
{
authModel.Message = "Email or Password is incorrect!";
return authModel;
}
var jwtSecurityToken = await CreateJwtToken(user);
var rolesList = await _userManager.GetRolesAsync(user);
authModel.IsAuthenticated = true;
authModel.Token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
authModel.Email = user.Email;
authModel.Username = user.UserName;
//authModel.ExpiresOn = jwtSecurityToken.ValidTo;
authModel.Roles = rolesList.ToList();
if (user.RefreshTokens.Any(t => t.IsActive))
{
var activeRefreshToken = user.RefreshTokens.FirstOrDefault(t => t.IsActive);
authModel.RefreshToken = activeRefreshToken.Token;
authModel.RefreshTokenExpiration = activeRefreshToken.ExpiresOn;
}
else
{
var refreshToken = GetRefreshToken();
authModel.RefreshToken = refreshToken.Token;
authModel.RefreshTokenExpiration = refreshToken.ExpiresOn;
user.RefreshTokens.Add(refreshToken);
await _userManager.UpdateAsync(user);
}
return authModel;
}
Endpoint:
[HttpPost]
public async Task<ActionResult> Login([FromBody]UserLoginDto loginDto)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var result = await _authService.GetTokenAsync(loginDto);
if (!string.IsNullOrEmpty(result.RefreshToken))
{
_authService.SetRefreshTokenInCookie(result.RefreshToken, result.RefreshTokenExpiration);
}
return Ok(result);
}

Related

System.Threading.Timer: A second operation was started on this context with

I have Console Application on .NET 5 which is Discord Bot.
While start the application i have the problem with this:
System.InvalidOperationException: A second operation was started on
this context before a previous operation completed. This is usually
caused by different threads concurrently using the same instance of
DbContext. For more information on how to avoid threading issues with
DbContext
And this is happens in Timer
This is in constructor:
_approveTimer = new Timer(async delegate
{
await CheckApproveAsync();
}, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
public async Task CheckApproveAsync()
{
var pendingUsers = await _pendingUsers.GetAllAsync();
if (pendingUsers is null || !pendingUsers.Any())
return;
Program.Log.Debug($"Checking {pendingUsers.Count} pending users...");
foreach (var user in pendingUsers)
{
var expired = DateTime.UtcNow > user.ExpirationTime;
if (!expired) continue;
await _pendingUsers.DeleteWithDetachAsync(user);
IonicHelper.GetGuildUserById(_mainGuildId, user.UserId, out var sgUser);
try
{
var msg = await _messageService.GetMessageAsync("register-pending-expired", new FormatData(user.UserId));
await sgUser.SendIonicMessageAsync(msg);
}
catch (Exception ex)
{
await _serverHelper.SendLogAsync(_mainGuildId, "Error", $"{nameof(CheckApproveAsync)} - {ex.Message}");
}
}
var usersValidated = 0;
foreach (var user in pendingUsers)
{
RequestResult codeResult;
string code;
try
{
(codeResult, code) = await GetThirdPartyCodeByEncryptedSummonerIdAsync(user.Region, user.SummonerId);
}
catch (Exception)
{
continue;
}
if (code is null || codeResult != RequestResult.Success)
continue;
var sanitizedCode = new string(code.Where(char.IsLetterOrDigit).ToArray());
if (sanitizedCode != user.ConfirmationCode)
continue;
var (requestResult, summoner) = await GetSummonerByEncryptedPuuIdAsync(user.Region, user.PlayerUUID);
if (requestResult != RequestResult.Success)
{
await _pendingUsers.DeleteWithDetachAsync(user);
continue;
}
var (rankResult, rankData) = await GetLeaguePositionsByEncryptedSummonerIdAsync(user.Region, summoner.Id);
var soloqRank = GetRankModelFromEntry(rankData.FirstOrDefault(x => x.QueueType == "RANKED_SOLO_5x5"));
var summonerIcon = GetSummonerIconUrlById(summoner.ProfileIconId);
var lolData = new LeagueData
{
UserId = user.UserId,
SummonerRegion = user.Region,
PlayerUUID = summoner.Puuid,
AccountId = summoner.AccountId,
SummonerId = summoner.Id,
SummonerName = summoner.Name,
SummonerIcon = summonerIcon,
SummonerLevel = summoner.SummonerLevel,
SummonerRank = $"{soloqRank.Tier} {soloqRank.Rank}"
};
_ = IonicHelper.GetGuildUserById(_mainGuildId, user.UserId, out var sgUser);
await AssignRoleFromRankAsync(sgUser, soloqRank.Tier);
var data = await _leagueRepository.GetByIdAsync(user.UserId);
if (data == null)
{
await _leagueRepository.AddAsync(lolData);
}
usersValidated++;
user.SummonerName = lolData.SummonerName;
await PostValidateAsync(user);
}
Program.Log.Information($"{usersValidated} users validated.");
}
I read that it can be problem that if some method is not awaited, but i've checked it's all awaited. Which suggestions about this?

How to post form-data IFormFile to API from Controller?

received null in api IFormFile.
Javascript:
var archivo = _('inputCargar').files[0];
const form = new FormData();
form.append("files", archivo);
const config = {
headers: {
'content-type': 'multipart/form-data'}}
await axios.post(`${data.urlUtil}Insert_FileAzure`, form, config);
The form file arrive to the controller but cant arrive to the api
Controller in Front:
public async Task<JsonResult> Insert_FileAzure([FromForm] ICollection<IFormFile> files)
{
var result = await _apiUtil.PostAsync<ICollection<IFormFile>> ("File/UploadAzure", files);
return Json(result);
}
API:
[HttpPost]
public async Task<ActionResult<RespuestaUpload>> UploadAzure([FromForm(Name = "files")] ICollection<IFormFile> files)
{
if (files == null || files.Count == 0)
return Content("file not selected");
var respuestaUpload = await _utiles.UploadFilesAzure(files);
return Ok(respuestaUpload);
}
Use MultipartFormDataContent as httpContent like below:
var Client = new HttpClient();
var multipartFormDataContent = new MultipartFormDataContent();
foreach (IFormFile file in files)
{
byte[] fileData;
using (var reader = new BinaryReader(file.OpenReadStream()))
{
fileData = reader.ReadBytes((int)file.OpenReadStream().Length);
}
var fileContent = new ByteArrayContent(fileData);
multipartFormDataContent.Add(fileContent, "files", file.FileName);
}
var response = await Client.PostAsync(requestUrl, multipartFormDataContent);

JWT token with role. ASP.NET Core & AngularJS

I have working example of JWT Token. It is work good and when I put this token to storage in angularJS I can go to api controller with attribute [Authorize]. But when I generate token with role, I cant go to attribute [Authorize(Roles = "Admin")]. As I know I role save in token and I need`t to change a header of request to api. My code below
public class AuthOptions
{
public const string ISSUER = "MyAuthServer";
public const string AUDIENCE = "http://localhost:51489/";
const string KEY = "mysupersecret_secretkey!123";
public const int LIFETIME = 60;
public static SymmetricSecurityKey GetSymmetricSecurityKey()
{
return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KEY));
}
}
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task Login([FromBody]LoginViewModel model)
{
var identity = await GetIdentity(model.Email, model.Password);
if (identity == null)
{
Response.StatusCode = 400;
await Response.WriteAsync("Invalid username or password.");
return;
}
var now = DateTime.UtcNow;
var jwt = new JwtSecurityToken(
issuer: AuthOptions.ISSUER,
audience: AuthOptions.AUDIENCE,
notBefore: now,
claims: identity.Claims,
expires: now.Add(TimeSpan.FromMinutes(AuthOptions.LIFETIME)),
signingCredentials: new
SigningCredentials(AuthOptions.GetSymmetricSecurityKey(),
SecurityAlgorithms.HmacSha256));
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
access_token = encodedJwt,
username = identity.Name,
};
Response.ContentType = "application/json";
await Response.WriteAsync(JsonConvert.SerializeObject(response, new
JsonSerializerSettings { Formatting = Formatting.Indented }));
return;
}
private async Task<ClaimsIdentity> GetIdentity(string username, string
password)
{
var user = _db.User.FirstOrDefault(x => x.Email == username);
if (user != null)
{
var checkPass = _userManager.CheckPasswordAsync(user, password);
if (!checkPass.Result)
return null;
var userRoles = await _userManager.GetRolesAsync(user);
string role = userRoles[0];
var claims = new List<Claim>
{
new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email),
new Claim(ClaimsIdentity.DefaultRoleClaimType, role)
};
ClaimsIdentity claimsIdentity =
new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
return claimsIdentity;
}
return null;
}
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters =
newTokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = AuthOptions.ISSUER,
ValidateAudience = true,
ValidAudience = AuthOptions.AUDIENCE,
ValidateLifetime = true,
IssuerSigningKey =AuthOptions.GetSymmetricSecurityKey(),
ValidateIssuerSigningKey = true,
};
});
Put to storage with angularJS $cookies
$http.defaults.headers.common['Authorization'] = 'Bearer ' +
response.data.access_token;
With this atribute is working
[Authorize]
With this atribute not working
[Authorize(Roles = "Admin")]
You are storing your Role as a claim in the token.
You will need to create a policy that works of the role claim that you have assigned to your token.
Create a policy in your Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => policy.RequireClaim("Role", "Admin"));
});
Then you can use this authorization attribute [Authorize(Policy = "Admin")]

LoginAsync using Access Token and MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory

I'm getting a "You do not have permission to view this directory or page." error when I try to LoginAsync with an access token and MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory. This works with the equivalent form with MobileServiceAuthenticationProvider.MicrosoftAccount. I'm not sure why this isn't working. Is there a configuration I'm missing?
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
"https://login.microsoft.com",
"https://login.microsoftonline.com/3dd13bb9-5d0d-dd2e-9d1e-7a966131bf85");
string clientId = "6d15468d-9dbe-4270-8d06-a540dab3252f";
WebTokenRequest request1 = new WebTokenRequest(msaProvider, "User.Read", clientId);
request1.Properties.Add("resource", "https://graph.microsoft.com");
WebTokenRequestResult result =
await WebAuthenticationCoreManager.RequestTokenAsync(request1);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
var token = result.ResponseData[0].Token;
var token1 = new JObject
{
{ "access_token", token }
};
var user = await App.mobileServiceClient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, token1);
I was able to get MSAL.NET to work for this per code below. The key is the { resourceId + "/user_impersonation" } scope.
PublicClientApplication pca = new PublicClientApplication(clientId)
{
RedirectUri = redirectUri
};
string[] scopes = { resourceId + "/user_impersonation" };
var users = await pca.GetAccountsAsync();
var user = users.FirstOrDefault();
AuthenticationResult msalar = await pca.AcquireTokenAsync(
scopes, user, UIBehavior.ForceLogin, "domain_hint=test.net");
payload = new JObject
{
["access_token"] = msalar.AccessToken
};
mobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Reference: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/660#issuecomment-433831737

Refresh ClaimsPrincipal with OpenIDConnect Middleware, MVC Client, Cookies

Is there a standard way to ask an MVC client using ID4 OIDC middleware to refresh the ClaimsPrincipal when using cookies? It would be nice to just ask the middleware to refresh the ClaimsPrincipal but I don't think that functionality exists.
The code below does work, however, there is no nonce used in the example below - so I'm not sure if that's secure. I'm not sure how the middleware creates the nonce.
Does anyone have an example of properly refreshing the ClaimsPrincipal in an MVC client application using cookies with ID4 OIDC middleware?
Validate ID token and Return ClaimsPrincipal from ID Token
private ClaimsPrincipal ValidateIdentityToken(string idToken, DiscoveryResponse disco )
{
var keys = new List<SecurityKey>();
foreach (var webKey in disco.KeySet.Keys)
{
var e = Base64Url.Decode(webKey.E);
var n = Base64Url.Decode(webKey.N);
var key = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n });
key.KeyId = webKey.Kid;
keys.Add(key);
}
var parameters = new TokenValidationParameters
{
ValidIssuer = disco.TryGetString(OidcConstants.Discovery.Issuer),
ValidAudience = "mvc.hybrid",
IssuerSigningKeys = keys,
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken token;
var user = handler.ValidateToken(idToken, parameters, out token);
//var nonce = user.FindFirst("nonce")?.Value ?? "";
//if (!string.Equals(nonce, "random_nonce")) throw new Exception("invalid nonce");
//nonce is always ""
return user;
}
Check cookie expiration. Use the refresh token to refresh ID and Access token. Use the above validation to return ClaimsPrincipal
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromMinutes(60),
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async cookiecontext =>
{
if (cookiecontext.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(cookiecontext.Properties.Items[".Token.expires_at"]);
if (expire <= DateTime.Now.AddMinutes(-5) || DateTime.Now > expire)
{
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError) throw new Exception(disco.Error);
var refreshToken = cookiecontext.Properties.Items[".Token.refresh_token"];
var tokenClient = new TokenClient(disco.TokenEndpoint,
"mvc.hybrid",
"secret");
var response = await tokenClient.RequestRefreshTokenAsync(refreshToken);
if (!response.IsError)
{
cookiecontext.Properties.Items[".Token.access_token"] = response.AccessToken;
cookiecontext.Properties.Items[".Token.refresh_token"] = response.RefreshToken;
cookiecontext.Properties.Items[".Token.expires_at"] = DateTime.Now.AddSeconds((int)response.ExpiresIn).ToString();
cookiecontext.Properties.Items["NextAccessTokenRefresh"] = DateTime.Now.AddMinutes(5).ToString();
var _Princ = ValidateIdentityToken(response.IdentityToken, disco);
cookiecontext.ReplacePrincipal(_Princ);
cookiecontext.ShouldRenew = true;
}
else
{
cookiecontext.RejectPrincipal();
}
}
}
}
}
});
Wireup ID4 middleware
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc.hybrid",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "openid", "profile", "email", "api1", "offline_access", "role" },
GetClaimsFromUserInfoEndpoint = true,
Events = new OpenIdConnectEvents()
{
OnTicketReceived = notification =>
{
notification.Response.Cookies.Append("NextAccessTokenRefresh", DateTime.Now.AddMinutes(5).ToString());
return Task.FromResult(0);
}
},
SaveTokens = true,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
}
});
}

Resources