ASP.NET Core 5 MVC web application - Login via xamarin - api

I've developed my ASP.NET Core 5 MVC application with "Individual Login". Registering and logging within the app works fine.
Now I want to log in to my MVC web application with an API for my Xamarin App. From what I've read "JWT" should be used. I want to use as much "standard" in the backend as possible, ideally using standard APIs.
Unfortunately, all the sites I've tried could not help me (solution broken, non-existing urls,....).
Could somebody please post me a working tutorial or an example for the backend please.
Thanks, Jeppen

From api, you can configure the jwt authentication as this.
In Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
//The previous three items are required
ValidIssuer = "http://localhost:5000",
ValidAudience = "api",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("this is a long key"))
/***********************************default TokenValidationParameters parameter***********************************/
// RequireSignedTokens = true,
// SaveSigninToken = false,
// ValidateActor = false,
};
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
//...
}
Apply for a token, generate a string token in the action.
public IActionResult Authenticate()
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("this is a long key");
var authTime = DateTime.UtcNow;
var expiresAt = authTime.AddDays(7);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(JwtClaimTypes.Audience,"api"),
new Claim(JwtClaimTypes.Issuer,"http://localhost:5000"),
new Claim(JwtClaimTypes.Id, "10"),
new Claim(JwtClaimTypes.Name, "my name"),
new Claim(JwtClaimTypes.Email, "email"),
}),
Expires = expiresAt,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(tokenString);
}
Xamarin App receives token and save it. When Xamarin App access the authorized resource, it can carray this token with this header.
var client = new HttpClient();
var token = client.GetAsync("[url that get the token] ");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
client.GetAsync("[url that get the authorized resource] ");

Related

How manually validate a JWT Access Token emitted by Microsoft Login Provider?

I got an Angular application that let's users login with a Microsoft and Google Account. Once the login step is over, I got an Access Token which is sent to the API for validation to secure the application. The API is a ASP.net Core 6 application.
I successfully validate an Access Token emitted by Google with the code below. But, I need to do the same with an Access Token emitted by Microsoft.
builder.Services.AddAuthentication(x =>
{
x.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SecurityTokenValidators.Add(new MicrosoftTokenValidator()); // todo
options.SecurityTokenValidators.Add(new GoogleTokenValidator());
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings.AccessTokenSecretKey)),
ValidateIssuer = false,
ValidateAudience = false,
ValidIssuer = appSettings.AccessTokenValidIssuer,
ValidAudience = appSettings.AccessTokenValidAudience
};
});
With Google things are easy with GoogleJsonWebSignature.ValidateAsync from using Google.Apis.Auth;
public class GoogleTokenValidator : ISecurityTokenValidator
{
private readonly JwtSecurityTokenHandler _tokenHandler;
public GoogleTokenValidator()
{
_tokenHandler = new JwtSecurityTokenHandler();
}
public bool CanValidateToken => true;
public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
public bool CanReadToken(string securityToken)
{
return _tokenHandler.CanReadToken(securityToken);
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
validatedToken = null;
var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result; // here is where I delegate to Google to validate
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, payload.Name),
new Claim(ClaimTypes.Name, payload.Name),
new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
new Claim(JwtRegisteredClaimNames.Email, payload.Email),
new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
};
try
{
var principle = new ClaimsPrincipal();
principle.AddIdentity(new ClaimsIdentity(claims, "Password"));
return principle;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
I try to find the same validation pattern for Microsoft Provider but without any success. This is why I'm asking the the SO community to get some answers or some help.
I found Microsoft Graph API and Azure Identity, but it leads me nowhere.
I found the answer to my question. Yesterday, I was thinking Microsoft could have done something like GoogleJsonWebSignature and I was trying to find it. It was a mistake!
I realized I can decode every access token with System.IdentityModel.Tokens.Jwt found at https://jwt.io/libraries.
I can then read the tokens this way inside public ClaimsPrincipal ValidateToken method and then write my validation logic :
var token = new JwtSecurityToken(securityToken);

Asp.Net core web API anti-forgery validation fails if used along with JWT bearer authentication

I am trying to use Anti-forgery along with jwt bearer authentication in Asp.net core 3.0 web API. The weird problem that I am facing is that anti-forgery works perfectly fine, but if I try to add an [Authorize] filter to the controller action along with [ValidateAntiForgeryToken], then AntiForgery validation fails with Http 400 error.
startup.cs :
services.AddCors();
services.AddControllers();
services.AddMvc();
services.AddAntiforgery
(
options =>
{
options.HeaderName = "X-XSRF-TOKEN";
options.Cookie = new Microsoft.AspNetCore.Http.CookieBuilder()
{ Name = "X-XSRF-COOKIE" };
}
);
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
UsersController.cs :
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
var user = _userService.Authenticate(model.Username, model.Password);
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
Response.Cookies.Append("X-XSRF-TOKEN", tokens.RequestToken, new Microsoft.AspNetCore.Http.CookieOptions
{
HttpOnly = false
});
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
return Ok(user);
}
If I use [Authorize] filter on this below action Antiforgery validation fails.If I remove it Antiforgery validation seems to be working fine.
UsersController.cs :
[HttpGet]
[Authorize]
[ValidateAntiForgeryToken]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
and this is how I am generating JWT the token :
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
servers logs :
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "GetAll", controller = "Users"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult GetAll() on controller WebApi.Controllers.UsersController (WebApi).
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter[1]
Antiforgery token validation failed. The provided antiforgery token was meant for a different claims-based user than the current user.
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user.
at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter'.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ProblemDetails'.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action WebApi.Controllers.UsersController.GetAll (WebApi) in 28.5729ms
Tried setting HttpContext.user before calling _antiforgery.GetAndStoreTokens(HttpContext) but it did not worked.
You need to setup antiforgeryvalidation in the startup file before going into controller. See the below code snippet:
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// The request token can be sent as a JavaScript-readable cookie,
// and Angular uses it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}

.net Core 3 Web API JWT unauthorized

I am beginner in the world of authentication and I am trying to implement a simple example to secure my api.
In my Login action I generate a JWT like:
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("ThIsIsAKeyT03ncrypt!4lw4ysUs3AStr0ng1"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "https://localhost:44392/",
audience: "https://localhost:44392/",
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
My controller is tagged as follows:
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
and the StartUp class handles the authentication like:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
and:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "https://localhost:44392/",
ValidAudience = "https://localhost:44392/",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("ThIsIsAKeyT03ncrypt!4lw4ysUs3AStr0ng1")),
ValidateLifetime = true
};
});
The problem is that although I am using the same symmetric key, issuer and audience, when testing through postman, the result is always status 401.
Is there a setting/option I am missing? Any help would be welcome
Problem solved by adding
services.AddAuthentication(cfg =>
{
cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
in the StartUp class.

.NET Core 2 Web API JWT token not recognized

I followed this tutorial to configure JWT authorization in my Web API app. The token generation and handout works fine, but when I send a request back to the server with the token, it doesn't populate the identity, so it fails if authorization is required.
I've tested both with a reactjs frontend and Postman. Both end up returning nothing (without Authorize decorator - User.Identity.isAuthorized is false), or 404 with the decorator. I have confirmed that the token is being sent properly.
I'm also using Identity, if that matters.
ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
}
Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseCors("SiteCorsPolicy");
app.UseMvc();
...
}
Function to build the token
private string BuildToken(AuthViewModel user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken
(
_config["Jwt:Issuer"],
_config["Jwt:Audience"],
//claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Excerpt from appsettings.json
"Jwt": {
"Key": "<secret stuff>",
"Issuer": "http://localhost:53530/",
"Audience": "http://localhost:8080/"
}
Test function I'm trying to call but is failing
[HttpGet("currentuser"), Authorize]
public async Task<ApplicationUser> GetCurrentUser()
{
var username = User.Identity.Name;
return await _context.ApplicationUsers.SingleOrDefaultAsync(u => u.UserName == username);
}
I figured it out. I had to add a new Authorization Policy.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
Then I decorated the controller with
[Authorize("Bearer"]
I've been messing with this for a couple days, trying different tutorials, so I know this was working at one point without the policy. Dunno why I needed it this time or why it wasn't part of the tutorial.
If someone figures out what I screwed up in the first place, I'm all ears.
I ran into the same issue (.net core 2.1) and was really happy to make it work using your answer #atfergs.
After fiddling with the whole setup I found out that no new Authorization Policy is required.
It is sufficient to decorate the controller with
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
considering the following setup
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{...}
Now
User?.Identity?.IsAuthenticated
is true :)
Cheers!

How can I implement Bearer Token in MVC 6 API vNext?

I am working on a sample SPA application to get my hands on ASP.NET 5. I am using Visual Studio Community 2015 RC.
I am stuck on Bearer token generation. I need to generate a token for AngularJS app so that I can call and authenticate APIs.
Have a look at this similar question Token Based Authentication in ASP.NET Core
Matt DeKrey's answer may solve your problem.
You can implement claim based authentication like below;
Add a method in Startup.cs
public void ConfigureAuthentication(IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes("very-secret-much-complex-secret");
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
// Validate the JWT issuer (Iss) claim
ValidateIssuer = false,
//ValidIssuers = validIssuerList,
// Validate the JWT audience (Aud) claim
ValidateAudience = false,
//ValidAudiences = validAudienceList,
// Validate token expiration
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
});
}
And call this method in ConfigureServices method on Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//DI Injections
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IAudienceService, AudienceService>();
ConfigureAuthentication(services);
services.AddMvc(
options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
Then, UseAuthentication in the Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// 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.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
}
Above we configured our API to use JWT authentication as authorization layer. Lets see how we generate a valid token below;
public async Task<string> Authenticate(string apiKey, string sharedSecret)
{
//get audience by apikey and password from database
//create token from createdobject
var audience = await audienceService.GetByCredentials(apiKey, sharedSecret);
// return null if auudience not found
if (audience == null)
return null;
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("very-secret-much-complex-secret");
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature);
//arange claims from permissions
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, audience.Name),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(audience.Permissions.Where(p => p.Value).Select(p => new Claim(ClaimsIdentity.DefaultRoleClaimType, p.Key.GetHashCode().ToString())));
var token = new JwtSecurityToken(
audience.Name,
audience.Name,
claims,
expires: DateTime.UtcNow.AddDays(7),
signingCredentials: signingCredentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
You can find the whole project in my GitHub repo:https://github.com/ilkerkaran/simple-claim-based-auth