Two factor auth with IdentityServer3 - remember browser - identityserver3

I'm implementing 2fa with IdentityServer3 + Asp.Net Identity (2.2.1). I'm stuck on the 2fa implementation. I've looked at the "AspNetIdentity_2fa" sample, which helped a lot.
I have everything wired up, except for the cookie that indicates the browser has been successfully authenticated. I can set the cookie during the code confirmation, but I cannot get to the cookie in the PostAuthenticateLocalAsync() call to see whether or not to take the 2fa path.
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled) // && !TwoFactorCookieSet...
{
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
I believe I'm taking the correct approach in using the partial logins, but how would I detect that the current browser has already been approved?
More detail: the /auth/sendcode is the standard Asp.Net Identity pages/flow for 2fa, combined with the partial login logic from the sample.

Okay, I found that OwinEnvironmentService can be injected into IdentityServer services. I can get the cookies via OwinEnvironmentService. I'd be interested to hear any opinions on this solution (this isn't meant to be production-ready, it's just a concept):
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var twoFactorNeeded = false;
object httpContext;
if (_owinEnvironmentService.Environment.TryGetValue("System.Web.HttpContextBase", out httpContext))
{
var cookies = (httpContext as HttpContext)?.Request.Cookies;
if (cookies != null && !cookies.AllKeys.Contains(IdentityConstants.CookieNames.TwoFactorCompleted)) twoFactorNeeded = true;
}
if (twoFactorNeeded)
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
}
UPDATED
Based on Brock's comment, I think I have a better solution.
// custom User Service
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override async Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var owinContext = new OwinContext(_owinEnvironmentService.Environment);
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
if(result == null) return new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName);
}
return await base.PostAuthenticateLocalAsync(user, message);
}
}
// (in MVC controller) generate the 2FA security code and send it
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
// ...some code removed for brevity...
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, model.SelectedProvider);
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, model.SelectedProvider, token);
if (!identityResult.Succeeded) return View("Error");
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, model.ReturnUrl, model.RememberMe });
}
// (in MVC controller) verify the code and sign in with 2FA
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
// ...some code removed for brevity...
var signInManager = new SignInManager<User, string>(UserManager, Request.GetOwinContext().Authentication);
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, model.Provider, model.Code))
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await signInManager.SignInAsync(user, model.RememberMe, model.RememberBrowser);
var resumeUrl = await env.GetPartialLoginResumeUrlAsync();
return Redirect(resumeUrl);
}
else
{
await UserManager.AccessFailedAsync(user.Id);
ModelState.AddModelError("", "Invalid code.");
return View(model);
}
}

I implemented the same for remember browser requirement however following statement return always null when we logout and login again.so twofactory step is not skipped..
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

Related

How do I get the JWT in a Blazor Server App with Microsoft Identity Platform (AAD) to make external API-Management call and authorize with the jwt

The situation I have:
Blazor Server App , .Net6.0.9 with Microsoft Identity Platform.
Blazor Server App is registered in the App Registration on Tenant-1
Client-API-1 is also resigered in the App Registration on Tenant-1
Login actions are done against/with the ClientId of the Client-API-1 registration and work fine.
In the API-Management I've added on the Inbound processing Polecies Validate-jwt like this:
(source of Microsoft)
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid!!!">
<openid-config url="https://login.microsoftonline.com/11a14169-89cc-44e8-95d7-xxxxxxxxxxxx/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud">
<value>{client-id-of-Client-API-1-on-App-Registration}</value>
</claim>
</required-claims>
</validate-jwt>
In Service looks like this:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Identity.Web;
using System.Net.Http.Headers;
namespace BlazorSAAppJwt.Data
{
public class ApimService : IApimService
{
private AuthenticationStateProvider _authenticationStateProvider { get; set; }
private readonly ITokenAcquisition _tokenAcquisition;
public ApimService(AuthenticationStateProvider AuthenticationStateProvider, ITokenAcquisition tokenAcquisition)
{
_authenticationStateProvider = AuthenticationStateProvider;
_tokenAcquisition = tokenAcquisition;
}
//public async Task<string?> GetResponseAsync(string path, CancellationToken cancellationToken)
public async Task<string?> GetResponseAsync(string path)
{
try
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
if (authState?.User?.Identity?.IsAuthenticated ?? false)
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://apimanagement.azure-api.net/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("email", authState.User.Identity.Name);
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "{My APIM suvbscriptionkey}"); // APIM
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Trace", "true");
// This gets the UserToken to get data from Microsoft Graph for the scopes: User.Read & Mail.Read
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "Mail.Read" });
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
var dataRequest = await httpClient.GetAsync("https://graph.microsoft.com/beta/me");
string? userDisplayName = "";
if (dataRequest.IsSuccessStatusCode)
{
var userData = System.Text.Json.JsonDocument.Parse(await dataRequest.Content.ReadAsStreamAsync());
userDisplayName = userData.RootElement.GetProperty("displayName").GetString();
}
//Remove the previous Authorization-header for the Microsoft Graph call
httpClient.DefaultRequestHeaders.Remove("Authorization");
//Add the Application token to the Authorization for APIM
//NOTE!!! Here is where the JWT token should be used!!!!
string jwt = "How do I get the jwt here to add and send to the APIM";
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
//HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}", cancellationToken);
HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}");
if (response.IsSuccessStatusCode)
{
string clientApiResult = await response.Content.ReadAsStringAsync();
return clientApiResult;
}
else
{
throw new UnauthorizedAccessException($"(Graph) User Display Name: {userDisplayName}" +
$"{Environment.NewLine}Response from APIM call: {response}");
}
}
else
{
// "The user is NOT authenticated.";
throw new UnauthorizedAccessException();
}
return default;
}
catch (Exception ex)
{
var iets = ex.Message;
throw;
}
}
}
}
I receive the UserDisplayName from the Graph API-call.
My program.cs
using BlazorSAAppJwt.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
var azureSection = builder.Configuration.GetSection("AzureAd");
var microsoftGraphSection = builder.Configuration.GetSection("MicrosoftGraph");
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
//.AddMicrosoftGraph(microsoftGraphSection) // Nuget Microsoft.Identity.Web.MicrosoftGraph
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddTokenAcquisition();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<ApimService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
What do I miss, and how do I setup my Blasor Server App to use the JWT token?
EDIT:
The API-calls on the APIM is not going to change and will call the Client-Api that is not exposed to the internet.
Who knows that the call:
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "Mail.Read" });
Retruns the JWT... It does and what it is you need to use in the request header
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
And make the call
HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}", cancellationToken);

Set CookieAuthentication redirect path

I only want users with an LocationId to be able to acces my controller methods.
On the location index page the users enter their id, which is saved in a cookie.
If a user tries to acces an page without, the user should be redirecteded to the location index page.
This almost work, but I have a problem with the redirect.
I am using asp net core 2.0.
My controller looks like this
[AllowAnonymous]
public class LocationController : Controller
{
...
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Index(string id)
{
ILocationModel location = await _repo.GetLocation(id);
if (location != null)
{
var claims = new List<Claim> { new Claim(ClaimTypes.Name, location.id) };
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return RedirectToAction("index", "shop");
}
return RedirectToAction("", "");
}
And in configureServices() in startup I have:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ReturnUrlParameter = "";
options.AccessDeniedPath = "/Location/Index/";
options.LoginPath = "/Location/Index";
options.LogoutPath = "/Location/Logout";
});
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
When I access an page unauthorized I get redirected to http://localhost:54104/Location/Index?=%2FLocation%2FIndex%3F%3D%252FLocation%252FIndex%253F%253D%25252FLocation%25252FIndex%25253F%25253D%2525252FLocation%2525252FIndex%2525253F%2525253D%252525252FLocation%252525252FIndex%252525253F%252525253D%25252525252FLocation%25252525252FIndex%25252525253F%25252525253D%2525252525252FLocation%2525252525252FIndex%2525252525253F%2525252525253D%252525252525252FLocation%252525252525252FIndex%252525252525253F%252525252525253D%25252525252525252FLocation%25252525252525252FIndex%25252525252525253F%25252525252525253D%2525252525252525252FLocation%2525252525252525252FIndex%2525252525252525253F%2525252525252525253D%252525252525252525252FLocation%252525252525252525252FIndex%252525252525252525253F%252525252525252525253D%25252525252525252525252FLocation%25252525252525252525252FIndex%25252525252525252525253F%25252525252525252525253D%2525252525252525252525252FLocation%2525252525252525252525252FIndex%2525252525252525252525253F%2525252525252525252525253D%252525252525252525252525252FLocation%252525252525252525252525252FIndex%252525252525252525252525253F%252525252525252525252525253D%25252525252525252525252525252FLocation%25252525252525252525252525252FIndex%25252525252525252525252525253F%25252525252525252525252525253D%2525252525252525252525252525252FLocation%2525252525252525252525252525252FIndex%2525252525252525252525252525253F%2525252525252525252525252525253D%252525252525252525252525252525252FLocation%252525252525252525252525252525252FIndex%252525252525252525252525252525253F%252525252525252525252525252525253D%25252525252525252525252525252525252FLocation%25252525252525252525252525252525252FIndex%25252525252525252525252525252525253F%25252525252525252525252525252525253D%2525252525252525252525252525252525252FLocation%2525252525252525252525252525252525252FIndex%2525252525252525252525252525252525253F%2525252525252525252525252525252525253D%252525252525252525252525252525252525252FLocation%252525252525252525252525252525252525252FIndex%252525252525252525252525252525252525253F%252525252525252525252525252525252525253D%25252525252525252525252525252525252525252FLocation%25252525252525252525252525252525252525252FIndex
witch causes an
HTTP Error 404.15 - Not Found
The request filtering module is configured to deny a request where the query string is too long.
Why is all this appended to the path?
I had the same problem. It's creating an infinite loop. You have to set a RedirectUri in a AuthenticationProperties object, in your index method (the HttpPost one). Like so:
var auth = new AuthenticationProperties()
{
RedirectUri = "/index/shop"
};
It could be like:
[HttpPost]
public async Task<IActionResult> Index(string id)
{
ILocationModel location = await _repo.GetLocation(id);
var auth = new AuthenticationProperties()
{
RedirectUri = "/index/shop"
};
if (location != null)
{
var claims = new List<Claim> { new Claim(ClaimTypes.Name, location.id) };
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
// You have to create a ChallengeResult, otherwise it will be stuck there, and you send the user to where you want to
return new ChallengeResult("cookies", auth);
}
return new ChallengeResult("cookies", auth);
}
For more info: https://dotnetcoretutorials.com/2017/09/16/cookie-authentication-asp-net-core-2-0/

ASP.NET Core JWT and Claims

I have a question regarding JWT authentication in ASP.NET Core and Claims, because I don't know if I get everything correctly.
When I create a JWT token in ASP.NET I add some Claims, some of which can be custom. What happens when the request with JWT token is sent from the client to API. How is User.Claims filled ? Does it use the claims that are read from JWT?
I would like to create a custom Identity provider ( don't want to use this provided by ASP.NET), with my own tables for user data, roles etc. I don't want store all important data required to fulfill the policy in JWT token (the amount of information stored in token matters, as well as security matters). Is it possible to store only basic claims (like user id, name etc) in JWT token, and then re-fetch other required data DB/ Cache? Along with that, I would like to use the standard mechanism for [Authorize] and the Policy mechanism.
How to make this all work: Custom User Identity + JWT + Standard ASP.NET policy-based authorization + claims fetched from DB/Cache on every request? How to achieve this?
Asp Net Core
First step is write the method that configure Jwt authentication:
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime. As we're creating the tokens locally and validating
// them on the same machines which should have synchronised time,
// this can be set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
Now inside the ConfigureServices() method of the Startup.cs, we can call ConfigureJwtAuthService() method to configure the Jwt authentication.
This is the complete Startup.cs:
using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace ExpertCodeBlogWebApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add
// services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Configure jwt autenticazione
ConfigureJwtAuthService(services);
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
// Create the Autofac container builder for dependency injection
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Return ServiceProvider
var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
}
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime.
// As we're creating the tokens locally and validating them on the
// same machines which should have synchronised time, this can be
// set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
// This method gets called by the runtime. Use this method to configure
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
// For dependency injection.
public class AutofacModule : Module
{
// Dependency Injection with Autofact
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UserRepository>().As<IUserRepository>()
.SingleInstance();
}
}
}
The JwtController.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace ExpertCodeBlogWebApp.Controllers
{
[Route("api/[controller]")]
public class JwtController : Controller
{
#region Private Members
// JWT-related members
private TimeSpan TokenExpiration;
private SigningCredentials SigningCredentials;
// EF and Identity members, available through DI
private MyDbContext DbContext;
private IUserRepository _userRepository;
private readonly ILogger _logger;
#endregion Private Members
#region Static Members
private static readonly string PrivateKey = "my_PrivateKey";
public static readonly SymmetricSecurityKey SecurityKey =
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
public static readonly string Issuer = "my_Issuer";
public static readonly string Audience = "my_Audience";
#endregion Static Members
#region Constructor
// I have used Autofac in the Startup.cs for dependency injection)
public JwtController(
MyDbContext dbContext,
IUserRepository userRepository,
ILogger<JwtController> logger)
{
_logger = logger;
_userRepository = userRepository;
// Instantiate JWT-related members
TokenExpiration = TimeSpan.FromMinutes(10);
SigningCredentials = new SigningCredentials(SecurityKey,
SecurityAlgorithms.HmacSha256);
// Instantiate through Dependency Injection with Autofact
DbContext = dbContext;
}
#endregion Constructor
#region Public Methods
// Manages the request for a new authentication or the refresh of an
// already established one
[HttpPost("token")]
public async Task<IActionResult>
Authentication([FromBody]JwtRequestViewModel jwt)
{
if (ModelState.IsValid)
{
string grantType = jwt.GrantType;
if (grantType == "password")
{
string userName = jwt.UserName;
string password = jwt.Password;
// Password check required
var user = await
_userRepository.GetUserInfoWithCheckPwd(userName, password);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
if (UserEnabled(user))
return await GenerateToken(user);
else
return BadRequest("User name or password invalid.");
}
}
else if (grantType == "refresh_token")
{
string userName = jwt.UserName;
// Refresh token (no password check required)
var user = await _userRepository.GetUserInfoByName(userName);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
string token = jwt.Token;
if (token == user.Token)
{
// Generate token and send it via a json-formatted string
return await GenerateToken(user);
}
else
{
return BadRequest("User token invalid.");
}
}
else
return BadRequest("Authentication type invalid.");
}
else
return BadRequest("Request invalid.");
}
#endregion Public Methods
#region Private Methods
private bool UserExpired(Users utente)
{
if (utente != null)
return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
return true;
}
private bool UserEnabled(Users utente)
{
if (utente != null)
return utente.Enabled == true;
return false;
}
private JsonSerializerSettings DefaultJsonSettings
{
get
{
return new JsonSerializerSettings()
{
Formatting = Formatting.Indented
};
}
}
private async Task<IActionResult> GenerateToken(Users user)
{
try
{
if (user != null)
{
var handler = new JwtSecurityTokenHandler();
DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Name, "TokenAuth"),
new[] { new Claim("ID", user.Id.ToString())}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = JwtController.Issuer,
Audience = JwtController.Audience,
SigningCredentials = SigningCredentials,
Subject = identity,
Expires = newTokenExpiration
});
string encodedToken = handler.WriteToken(securityToken);
// Update token data on database
await _userRepository.UpdateTokenData(user.Name, encodedToken,
newTokenExpiration);
// Build the json response
// (I use Automapper to maps an object into another object)
var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
jwtResponse.AccessToken = encodedToken;
jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
return Ok(jwtResponse);
}
return NotFound();
}
catch(Exception e)
{
return BadRequest(e.Message);
}
}
#endregion
}
}
On my project I use Angular. For call JwtController method by Angular:
login(userName: string, password: string)
{
return this.getLoginEndpoint(userName, password)
.map((response: Response) => this.processLoginResponse(response));
}
getLoginEndpoint(userName: string, password: string): Observable<Response>
{
// Body
// JwtRequest is a model class that I use to send info to the controller
let jwt = new JwtRequest();
jwt.GrantType = "password";
jwt.UserName = userName;
jwt.Password = password;
jwt.ClientId = "my_Issuer";
// Post requiest (I use getAuthHeader that attach to the header the
// authentication token, but it can also be omitted because it is ignored
// by the JwtController
return this.http.post(this.loginUrl, JSON.stringify(jwt),
this.getAuthHeader(true))
}
protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
{
// Hera I use this.authService.accessToken that is a my service where
// I have store the token received from the server
let headers = new Headers({
'Authorization': 'Bearer ' + this.authService.accessToken });
if (includeJsonContentType)
headers.append("Content-Type", "application/json");
headers.append("Accept", `application/vnd.iman.v01+json,
application/json, text/plain, */*`);
headers.append("App-Version", "01");
return new RequestOptions({ headers: headers });
}
private processLoginResponse(response: Response)
{
// process the response..
}
On the controllers classes (or methods) that you want to be accessible only by authenticated users (not on your JwtController because its method must be accessible by all users) you can set:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
To call from Angular the controller method that require authentication, you need to attach the token into the header with the getAuthHeader() method.
I hope this post can help you.
yes it uses the claim stored in jwt token
look at the httpcontext object for claims that are stored in token when you created the token
this link can also be helpfull https://joonasw.net/view/adding-custom-claims-aspnet-core-2

Xamarin.Auth fails to complete with Trakt

I'm building an app as Trakt client using Xamarin. To authenticate users, I use Xamarin.Auth because its cross-platform. However, after the authentication succeeds, it doesn't call Completed event handler. The event is only called once I click on the Back button but it returns a null Account object and false IsAuthenticated.
I'm wondering if its because the redirect uri is invalid.
Please see my code below.
[assembly: ExportRenderer(typeof(LoginView), typeof(LoginViewRenderer))]
namespace ShowsCalendar.Droid.ViewRenderer
{
public class LoginViewRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
var context = Forms.Context;
var baseAddress = ConfigHelper.TraktAPIURL;
var auth = new OAuth2Authenticator(
clientId: ConfigHelper.ClientID,
redirectUrl: new Uri("urn:ietf:wg:oauth:2.0:oob"),
scope: "",
authorizeUrl: new Uri(baseAddress + "/oauth/authorize?response_type=code")
);
auth.AllowCancel = true;
auth.Completed += AuthenticateCompleted;
var intent = auth.GetUI(context);
context.StartActivity(intent);
}
private void AuthenticateCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
if (!e.IsAuthenticated)
{
return;
}
App.AccessToken = e.Account.Properties["access_token"].ToString();
AccountStore.Create().Save(e.Account, "Trakt");
}
}
}

Can't login with facebook in Windows Phone App

It was working fine before but It's not working even though I haven't changed anything in my Facebook related code. It is giving this error:
App doesn't give permission to given URL : The settings of app doesn't allow one or more of the given URL's. URLs must be Website's URL or Canvas URL...
Here is my FacebookLoginPage.cs:
namespace MyApp.Pages
{
public partial class FacebookLoginPage : PhoneApplicationPage
{
private string message;
public FacebookLoginPage()
{
InitializeComponent();
message = String.Empty;
this.Loaded += FacebookLoginPage_Loaded;
}
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
NavigationService.GoBack();
base.OnBackKeyPress(e);
}
private async void FacebookLoginPage_Loaded(object sender, RoutedEventArgs e)
{
if (String.IsNullOrEmpty(App.AccessToken))
{
App.isAuthenticated = true;
await Authenticate();
}
}
private FacebookSession session;
private async Task Authenticate()
{
//Facebook logini kontroli eğer login olduysa AccessToken ve bilgileri çeker.
try
{
if (App.FacebookSessionClient.LoginInProgress == true && !String.IsNullOrEmpty(message))
{
App.FacebookSessionClient.LoginInProgress = false;
}
else
{
session = await App.FacebookSessionClient.LoginAsync("user_about_me,read_stream");
App.AccessToken = session.AccessToken;
App.appSettings["accessToken"] = App.AccessToken;
App.appSettings.Save();
App.FacebookId = session.FacebookId;
Dispatcher.BeginInvoke(() => NavigationService.Navigate(new Uri("/Pages/MainPage.xaml?token=" + App.AccessToken, UriKind.Relative)));
}
}
catch (InvalidOperationException)
{
message = "failed";
App.FacebookSessionClient.LoginInProgress = true;
NavigationService.GoBack();
}
}
}
}
What might be the probelm?
When I added facebook.com to Oauth part of Advanced Setting in my Facebook App, the problem solved. Thanks to the following link: Windows Phone 8 Facebook Login Given URL is not allowed by the application