I want to call the Token Endpoint of IdentityServer 4 from my React App (running on http://localhost:3000). So in some login method I am doing:
login = () => {
const userdata = {
username: 'admin',
password: 'admin',
};
const dataForBody = `${'client_id=js&'}${'grant_type=password&' +
'username='}${encodeURI(userdata.username)}&` +
`password=${encodeURI(userdata.password)}&` +
`scope=${encodeURI('api1')}`;
const messageHeaders = {
'Content-Type': 'application/x-www-form-urlencoded',
};
axios({
method: 'post',
url: 'http://localhost:5000/connect/token',
headers: messageHeaders,
data: dataForBody,
})
.then((response) => {
console.log(response);
});
}
Now I am getting the following response:
{"error":"unauthorized_client"}
My IdSrv set up is something like the js application sample.
config.cs
namespace QuickstartIdentityServer
{
public class Config
{
// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:3000/login" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
public static List<TestUser> GetUsers()
{
return new List<TestUser> {
new TestUser {
SubjectId = "1", Username = "admin", Password = "admin"
},
};
}
}
}
startup.cs
namespace QuickstartIdentityServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
}
Am I missing something?
The problem is in the client definition:
AllowedGrantTypes = GrantTypes.Implicit,
is not correct. We have to use instead:
AllowedGrantTypes = ResourceOwnerPassword
The immediate problem that jumps out is that you are attempting to authenticate with the token service by passing the username and password as URL parameters. The client's username and password should be passed in using a standard basic authorization header:
Authorization: Basic Base64Encode(myusername:mypassword)
Which for this example would end up looking like this:
Authorization: Basic bXl1c2VybmFtZTpteXBhc3N3b3Jk
Related
I have trouble using Web Api, Javascript and gRPC Services...
First of all I recommend check this question to understand what I'm doing
Calling gRPC services through a Web API in .NET 6
Problem:
This is my controller:
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IConfiguration _config;
private readonly UserAuthClient _userClient;
public AuthController(ILogger<AuthController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_userClient = UserAuthServiceClientHelper.GetUserServiceClient(_config["RPCService:ServiceUrl"]);
}
[HttpPost("register")]
public async Task<ActionResult<NewUserResponse>> Register([FromBody] NewUserRequest user)
{
_logger.Log(LogLevel.Debug, "Request Received for AuthController::Register");
var results = await _userClient.AddUserAsync(user);
_logger.Log(LogLevel.Debug, "Sending Response from AuthController::Register");
return Created(string.Empty, results);
}
}
And this is Javascript code fetch
function registerJS() {
const register = document.getElementById('register');
const nome = document.getElementById('nome');
const email = document.getElementById('email');
const pass = document.getElementById('pass');
const item = {
Username: nome.value.trim(),
Email: email.value.trim(),
Password: pass.value.trim()
};
// api = 'api/Auth/register'
fetch(uri, {
//credentials: 'include',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => {
debugger;
if (response.status == 200) {
return response.json();
} else {
window.alert('Error, Motivo:' +
response.statusText());
throw new Error(response.status);
return;
}
})
.then((res) => {
debugger;
nome.value = '';
email.value = '';
pass.value = '';
window.alert('Successful Registration');
document.getElementById('LoginCheck').click();
})
.catch(error => {
debugger;
if (error.status == 400) {
window.alert('Bla Bla Bla 400_2) Register:' + error.statusText);
throw new Error(error.status);
return;
} else if (error.status == 401) {
window.alert('Bla Bla Bla 401_2) Register:' + error.statusText);
throw new Error(error.status);
return;
} else {
window.alert('It is not possible to register, Username or email already used
in the application');
throw new Error(error.status);
return;
}
});
}
So I'm calling a function of the web api (this is use gRPC Service method) with the fetch but it gives me error 404 idk why,
I test the method without the gRPC (simple web api using EF and javascript) and also test the gRPC service in console (both works fine) and now I want to use both same time and fails
I run first the gRPC server and next the web api
Also I don't know if the error can be because of the dll files but I need help because I think everything I implement its ok (rebuild gives me 0 errors)
Any help is welcome and sry if my english is bad
I am trying to call an asp.net core 5 API from a react code but I keep getting the error 415 from the server.
Here is my server endpoint where the frontend is trying to call
public class OauthToken
{
public string TokenId;
}
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin(OauthToken userView)
{
....
}
And the frontend code is as follows:
const googleResponse = (response) => {
const options = {
method: 'POST',
body: { TokenId: response.tokenId },
mode: 'no-cors',
accepts: "application/json",
cache: 'default',
contentType : "application/json",
}
fetch(config.GOOGLE_AUTH_CALLBACK_URL, options)
.then(r => {
console.log(r)
})
.catch(e=>{
console.log(e)
})
}
Below is my startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddCors(opts =>
{
opts.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
//.AllowCredentials();
});
});
services.AddAuthentication()
.AddIdentityServerJwt()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AppSettings:JwtSecret"])),
ValidateIssuer = false,
ValidateAudience = false
};
})
.AddGoogle(opt =>
{
opt.ClientId = "MY_CLIENT_ID";
opt.ClientSecret = "MY_CLIENT_SECRET";
opt.SignInScheme = IdentityConstants.ExternalScheme;
})
//.AddTwitter(twitterOptions => { })
.AddFacebook(facebookOptions => {
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
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.UseSpaStaticFiles();
app.UseRouting();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "/api/v1/{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
I don't know where the issue is coming from.
Please help me resolve this issue.
Thank you.
Based on your comment I have tested and reproduce your issue as you can have a look below:
Reproduced Issue:
How do I call the endpoint to get the data get to it?:
If you could have a look into your client-side code and HTTP Verb it should sent request within the FromBody but you are not sending that way, so you have two way to achieve that:
Way 1 : Set [FromBody] On Method:
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin([FromBody] OauthToken userView)
{
return Ok();
}
Output:
Way 2 : Set TokenId as string on Method:
[AllowAnonymous]
[HttpPost("signin-google")]
[Consumes("application/json")]
public async Task<IActionResult> GoogleLogin(string TokenId)
{
return Ok();
}
Output:
Note: So you could try above steps to call your API endpoint accordingly. I noticed that problem was in API routing and method argurment additionally, I would suggest you to have a look on our
offical docs for indepth insight here
Hope it would guide you as expected and help to resolve the issue.
I finally got the code working after making the following changes:
I noticed that the TokenId property of OathToken class had no getter and no setter. So, I updated it as follows:
public class OauthToken
{
//added {get; set;}
public string TokenId { get; set; }
}
Changed the body of the fetch request from an object to a blob as follows:
const tokenBlob = new Blob([JSON.stringify({ TokenId: response.tokenId }, null, 2)], { type: 'application/json' });
Changed the mode of the request from "no-cors" to "cors" since Cors is already declared at the startup.cs class for the project
So, the updated working fetch request is as follows:
const googleResponse = (response) => {
const tokenBlob = new Blob([JSON.stringify({ TokenId: response.tokenId }, null, 2)], { type: 'application/json' });
const options = {
method: 'POST',
body: tokenBlob,
mode: 'cors',
accepts: "application/json",
cache: 'default',
contentType : "application/json",
}
fetch(config.GOOGLE_AUTH_CALLBACK_URL, options)
.then(r => {
r.json().then(user => {
console.log(user.tokenId);
});
})
.catch(e=>{
console.log(e)
})
}
And the code at the endpoint is as follows:
public class OauthToken
{
public string TokenId { get; set; }
}
[AllowAnonymous]
[Consumes("application/json")]
[HttpPost("signin-google")]
public async Task<IActionResult> GoogleLogin([FromBody] OauthToken clientToken)
{
return Ok(clientToken);
}
I guess this might help someone else facing such issues.
Thank you
I am using HTML/Javascript in VS Code and using Dot net core 3.1 in Visual Studio 2019 for Web API development. Also using IIS on Windows 10 professional to test the API.
Developed login page with following code. Once user enters userid and password, clicks on login button, a web api "TestAuthService" is invoked.
function fnlogin() {
const uname = document.getElementById('uname').value;
const pwd = document.getElementById('pwd').value;
const logindata = {
username: uname,
password: pwd
}
const loginurl = 'http://localhost:8091/api/Auth/Login';
authenticate(loginurl, logindata);
}
async function authenticate(loginurl, logindata) {
console.log(logindata)
const response = await fetch(loginurl , {
method: "POST",
mode: "cors",
body: JSON.stringify(logindata),
headers: { "Content-type" : "application/json, charset=UTF-8"}
});
const rdata = await response.json();
console.log(rdata);
if (!rdata.success) {
document.getElementById("loginMessage").innerHTML = rdata.message;
return;
}
const inMemoryToken = rdata.data
localStorage.setItem('user', JSON.stringify(rdata));
window.location.href = "http://127.0.0.1:5500/Weatherinfo.html";
}
The API TestAuthService is published in IIS on localhost:8091. On successful login, a JWT is returned to the javascript. This works properly. The JWT is stored in localStorage in chrome browser by Javascript.
The auth controller code is as follows :
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IAuthRepository _authRepo;
public AuthController(IAuthRepository authRepo)
{
_authRepo = authRepo;
}
[HttpPost("Register")]
public async Task<ActionResult<ServiceResponse<int>>> Register(UserRegisterDto request)
{
var response = await _authRepo.Register(
new User { Username = request.Username }, request.Password
);
if (!response.Success)
{
return BadRequest(response);
}
return Ok(response);
}
[HttpPost("Login")]
public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
{
var response = await _authRepo.Login(
request.Username, request.Password
);
if (!response.Success)
{
return BadRequest(response);
}
return Ok(response);
}
The AuthRepository code is as follows :
public class AuthRepository : IAuthRepository
{
private readonly AppDbContext _context;
private readonly IConfiguration _configuration;
public AuthRepository(AppDbContext context, IConfiguration configuration)
{
_configuration = configuration;
_context = context;
}
public async Task<ServiceResponse<string>> Login(string username, string password)
{
var response = new ServiceResponse<string>();
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
if (user == null)
{
response.Success = false;
response.Message = "User not found.";
}
else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
{
response.Success = false;
response.Message = "Wrong password.";
}
else
{
response.Data = CreateToken(user);
}
return response;
}
public async Task<ServiceResponse<User>> Register(User user, string password)
{
ServiceResponse<User> response = new ServiceResponse<User>();
if (await UserExists(user.Username))
{
response.Success = false;
response.Message = "User already exists.";
return response;
}
CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
_context.Users.Add(user);
await _context.SaveChangesAsync();
response.Data = user;
return response;
}
public async Task<bool> UserExists(string username)
{
if (await _context.Users.AnyAsync(x => x.Username.ToLower().Equals(username.ToLower())))
{
return true;
}
return false;
}
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != passwordHash[i])
{
return false;
}
}
return true;
}
}
private string CreateToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokendDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = System.DateTime.Now.AddDays(1),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokendDescriptor);
return tokenHandler.WriteToken(token);
}
If the login is successful, the page Weatherinfo.html is displayed, This page has button "Get Weather data" , which when clicked, invokes another web api "weatherforecast"
<button type="button" onclick="return getWeather();">Get Weather data</button>
<table id="weatherdata">
<thead>
</thead>
<tbody id="weatherdatalist">
</tbody>
</table>
<script>
async function getWeather() {
const url = 'http://localhost:5861/weatherforecast';
const localstorage_user = JSON.parse(localStorage.getItem('user'));
const inMemToken = localstorage_user.data
console.log(inMemToken)
/*
const response = await fetch(url);
*/
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ${inMemToken}'
}
});
const data = await response.json();
console.log(data)
display(data);
}
function display(data) {
let tab = "";
data.forEach(element => {
tab += `<tr>
<td > ${element.date} </td>
<td> ${element.temperatureC} </td>
<td> ${element.temperatureF} </td>
<td> ${element.summary} </td>
</tr>`;
});
document.getElementById("weatherdatalist").innerHTML = tab;
}
</script>
The weatherforecast api has following code
Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("AllowOrigins", builder =>
{
builder.WithOrigins("http://localhost:5500", "http://127.0.0.1:5500")
.AllowAnyMethod()
.AllowAnyHeader();
}));
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
ValidateIssuer = false,
ValidateAudience = false
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseRouting();
app.UseCors("AllowOrigins");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The controller is as follows
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
The issue is the weatherforcast api fails with HTTP error 401 (Unauthorized)
I am not sure if I am passing the JWT correctly in the code below
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ${inMemToken}'
}
});
The issue seems to be resolved now.
Corrected the fetch api call as
const response = await fetch(url, {
headers: {
"Authorization": `Bearer ${inMemToken}`
}
});
``
This is a dotnet asp core 3 react application.
My startup.cs looks like this:
public class JwtAuthentication
{
public string SecurityKey { get; set; }
public string ValidIssuer { get; set; }
public string ValidAudience { get; set; }
public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Convert.FromBase64String(SecurityKey));
public SigningCredentials SigningCredentials => new SigningCredentials(SymmetricSecurityKey, SecurityAlgorithms.HmacSha256);
}
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
private readonly IOptions<JwtAuthentication> _jwtAuthentication;
public ConfigureJwtBearerOptions(IOptions<JwtAuthentication> jwtAuthentication)
{
_jwtAuthentication = jwtAuthentication ?? throw new System.ArgumentNullException(nameof(jwtAuthentication));
}
public void PostConfigure(string name, JwtBearerOptions options)
{
var jwtAuthentication = _jwtAuthentication.Value;
options.ClaimsIssuer = jwtAuthentication.ValidIssuer;
options.IncludeErrorDetails = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtAuthentication.ValidIssuer,
ValidAudience = jwtAuthentication.ValidAudience,
IssuerSigningKey = jwtAuthentication.SymmetricSecurityKey,
NameClaimType = ClaimTypes.NameIdentifier
};
}
}
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var connectionStringOs =
"Server=xx.xx.xx.xxIntegrated Security=false;Trusted_Connection=false;Database=Options;User Id=xx;Password=xx;Connection Timeout=60";
var connectionStringDs =
"Server=xx.xx.xx.x;Integrated Security=false;Trusted_Connection=false;Database=DY;User Id=xx;Password=xx";
services.AddDbContext<OptionsDbContext>(o =>
o.UseSqlServer(connectionStringOs));
services.AddDbContext<DYDbContext>(o =>
o.UseSqlServer(connectionStringDs));
//services.AddRazorPages();
services.AddMvc();
services.AddMvc(option => option.EnableEndpointRouting = false);
services.Configure<JwtAuthentication>(Configuration.GetSection("JwtAuthentication"));
// I use PostConfigureOptions to be able to use dependency injection for the configuration
// For simple needs, you can set the configuration directly in AddJwtBearer()
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
//public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app,
//IHostingEnvironment env,
IHostEnvironment env,
OptionsDbContext optionsDbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
//{
// HotModuleReplacement = true
//});
}
app.UseStaticFiles();
/// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
//app.UseCookiePolicy();
app.UseAuthorization();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int?}");
});
}
}
I add a controller to get a token:
using System;
using System.Linq;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using OptionsAPI.Entities;
[Route("user/[controller]")]
public class UserController : Controller
{
private readonly IOptions<JwtAuthentication> _jwtAuthentication;
public UserController(IOptions<JwtAuthentication> jwtAuthentication)
{
_jwtAuthentication = jwtAuthentication ?? throw new ArgumentNullException(nameof(jwtAuthentication));
}
[HttpPost]
[AllowAnonymous]
public IActionResult GenerateToken([FromBody]GenerateTokenModel model)
{
// TODO use your actual logic to validate a user
if (model.Password != "654321")
return BadRequest("Username or password is invalid");
var token = new JwtSecurityToken(
issuer: _jwtAuthentication.Value.ValidIssuer,
audience: _jwtAuthentication.Value.ValidAudience,
claims: new[]
{
// You can add more claims if you want
new Claim(JwtRegisteredClaimNames.Sub, model.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
},
expires: DateTime.UtcNow.AddDays(30),
notBefore: DateTime.UtcNow,
signingCredentials: _jwtAuthentication.Value.SigningCredentials);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
public class GenerateTokenModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
}
I have an html file to test this:
<script type="text/javascript">
const response = await fetch("http://www.awebsite.com/user/generatetoken", {
method: "POST",
body: JSON.stringify({
username: "foo#bar",
password: "654321"
}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
const json = await response.json();
const token = json.token;
console.log(token);
</script>
When I load this into a browser, nothing happens. Not sure how to test calling the controller that hands a token to be used by an API, or if this code is missing something to tie it all together?
The first thing is to use Fiddler or browser's developer tools to trace the request and check the error message . But keep in mind that in order to call a function using the await keyword, it must be within the async function :
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Accept": "application/json"
},
body: JSON.stringify(data)
});
return await response.json();
}
postData('http://www.awebsite.com/user/generatetoken', {
username: "foo#bar",
password: "654321"
})
.then((data) => {
console.log(data.token);
});
Or using :
fetch('http://www.awebsite.com/user/generatetoken', {
method: "POST",
body: JSON.stringify({
username: "foo#bar",
password: "654321"
}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
})
.then(response => response.json()).then(data => {
alert(data.token)
});
Take care of other potential issues like CORS .
can some please point where the issue is.
the problem that i encounter is i have a controller that i added an attribute Authorize. So, when i try to access the actionResult GETDATA it says unable to find the action. but if remove the attribute Authorize, it's working as expected.
So everytime i make a request i add a jwt token on the header.
Here are codes:
**Angular 8 HttpInterceptor**
const currentUser = this.authenticationService.currentUserValue;
//if (currentUser && currentUser.authData) {
if (currentUser && currentUser.Token) {
debugger;
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.Token}`,
CurrentTabID: `${currentUser.CurrentTabID}`
}
});
}
**MyController**
[Authorize]
[ApiController]
[Route("[controller]")]
public class PatientController : ControllerBase
{
[HttpGet("GetTestData")]
//--These is the one i can't access
public IActionResult GetTestData()
{
return Ok("");
}
[AllowAnonymous]
[HttpGet("GetTestDataOne")]
public IActionResult GetTestDataOne()
{
return Ok("Hi John");
}
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=.; Database=blah;persist security info=True;user id=blah;password=blah;"
},
"AllowedHosts": "*",
"ApplicationSettings": {
"Secret": "1234567890123456",
"ClientURL": ""
}
}
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<PPMPBookingContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:Secret"].ToString());
// configure strongly typed settings objects
//var appSettingsSection = Configuration.GetSection("AppSettings");
//services.Configure<AppSettings>(appSettingsSection);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer="vlad",
ValidAudience="Client"
};
});
// configure DI for application services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IPracticeService, PracticeService>();
}
// 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();
}
else
{
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.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
AccountController
public UserInfo Authenticate(int businessID, string username, string password)
{
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetSection("ApplicationSettings:Secret").Value);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.ID.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
userInfo.Token = tokenHandler.WriteToken(token);
byte[] bytes = Encoding.GetEncoding(28591).GetBytes($"{businessID}{username}");
userInfo.AuthData = System.Convert.ToBase64String(bytes);
user.Password = null;
userInfo.User = user;
userInfo.BusinessID = businessID;
userInfo.Practice = _practiceService.PracticeInfo(businessID);
userInfo.CurrentTabID = Guid.NewGuid().ToString();
return userInfo;
}