How to send token from ASP.net Web API to react js? - reactjs

I want to implement JWT Authentication in react js using web api.
I had created the JWT Authentication in web api.
It worked totally fine on Postman as I tested it.
When I am using it with react js the API is being hitted.
Now the problem is how do I send the token to react js and how do I fetch the token in react js
This is my Login Controller in web api
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Web;
using System.Web.Http;
using System.Web.Http.Cors;
using WEBAPI_JWT_Authentication.Models;
namespace WEBAPI_JWT_Authentication.Controllers
{
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class LoginController : ApiController
{
[HttpPost]
public IHttpActionResult Authenticate([FromBody] LoginRequest login)
{
var loginResponse = new LoginResponse { };
LoginRequest loginrequest = new LoginRequest { };
loginrequest.Username = login.Username.ToLower();
loginrequest.Password = login.Password;
IHttpActionResult response;
HttpResponseMessage responseMsg = new HttpResponseMessage();
bool isUsernamePasswordValid = false;
if(login != null)
isUsernamePasswordValid=loginrequest.Password=="test" ? true:false;
// if credentials are valid
if (isUsernamePasswordValid)
{
string token = createToken(loginrequest.Username);
var responseJSON = token;
//return the token
return Ok(responseJSON);
}
else
{
// if credentials are not valid send unauthorized status code in response
loginResponse.responseMsg.StatusCode = HttpStatusCode.Unauthorized;
response = ResponseMessage(loginResponse.responseMsg);
return response;
}
}
private string createToken(string username)
{
//Set issued at date
DateTime issuedAt = DateTime.UtcNow;
//set the time when it expires
DateTime expires = DateTime.UtcNow.AddDays(7);
//http://stackoverflow.com/questions/18223868/how-to-encrypt-jwt-security-token
var tokenHandler = new JwtSecurityTokenHandler();
//create a identity and add claims to the user which we want to log in
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
});
const string sec = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
var now = DateTime.UtcNow;
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey,Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature);
//create the jwt
var token =
(JwtSecurityToken)
tokenHandler.CreateJwtSecurityToken(issuer:"http://localhost:50191",audience:"http://localhost:50191",
subject: claimsIdentity, notBefore: issuedAt, expires: expires, signingCredentials: signingCredentials);
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
}
}
This is where I am fetching the token in react js
function login(username, password) {
return fetch(`${API_URL}/Login`, {username, passowrd})
.then(response => {
debugger;
if (!response.ok) {
return response;
}
return response.json();
})
.then(user => {
debugger;
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
Rather than this data if anyone knows how to send token to react js and how to fetch that token in react js, please do tell.

The way I do is to create a Response class
public class Response
{
public string Status { get; set; }
public string Message { get; set; }
public object Token { get; set; }
}
Depending on your need what you want to define in this class, for my cases, Status and Message is used to update progress status to front end.
You store your tokendictionary in class Response, and return it to the function. Lets say:
[HttpPost]
public IHttpActionResult Authenticate([FromBody] LoginRequest login)
{
var loginResponse = new LoginResponse { };
LoginRequest loginrequest = new LoginRequest { };
loginrequest.Username = login.Username.ToLower();
loginrequest.Password = login.Password;
IHttpActionResult response;
HttpResponseMessage responseMsg = new HttpResponseMessage();
bool isUsernamePasswordValid = false;
if(login != null)
isUsernamePasswordValid=loginrequest.Password=="test" ? true:false;
// if credentials are valid
if (isUsernamePasswordValid)
{
string token = createToken(loginrequest.Username);
Response Resp = new Response
{
Status = "Success",
Message = "User Login Successfully Change the Status Message here",
Token = tokenDictonary, //where you return token
};
return
}
else
{
// if credentials are not valid send unauthorized status code in response
loginResponse.responseMsg.StatusCode = HttpStatusCode.Unauthorized;
response = ResponseMessage(loginResponse.responseMsg);
return response;
}
}
and in your front end, you fetch your api.
I show you axios example
axios.post('http://yoururl', {
Token: this.state.Token,
})
.then(result => {
if (result.data.Status === 'Success') {
localStorage.setItem('Nameyourvariablehere', result.data.Token.tokentype);
// generate more if your token has more field
and then you are able to check your localStorage via getItem and setItem, I believe you know what to do for the following steps
Actually the way you create token is different from mine, I kind of follow this example.

Related

React and asp.net core authentication and authorization with google

I've been trying to get my authentication and authorization process working with google for two weeks now using react on the frontend and asp.net core on the backend.
Incredible as it may seem, I can't find a guide that addresses all this development context despite being something very basic.
Anyway, the best I could get was this code below, but I don't know how instead of returning the View, return my frontend in React.
And also how to do the process in react.
I know the question may sound a little generic, but I'm really lost.
This is my controller
public IActionResult GoogleLogin()
{
string redirectUrl = Url.Action("GoogleResponse", "Account");
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
}
[AllowAnonymous]
public async Task<IActionResult> GoogleResponse()
{
ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, false);
string[] userInfo = { info.Principal.FindFirst(ClaimTypes.Name).Value, info.Principal.FindFirst(ClaimTypes.Email).Value };
if (result.Succeeded)
return View(userInfo);
else
{
AppUser user = new AppUser
{
Email = info.Principal.FindFirst(ClaimTypes.Email).Value,
UserName = info.Principal.FindFirst(ClaimTypes.Email).Value
};
IdentityResult identResult = await userManager.CreateAsync(user);
if (identResult.Succeeded)
{
identResult = await userManager.AddLoginAsync(user, info);
if (identResult.Succeeded)
{
await signInManager.SignInAsync(user, false);
return View(userInfo);
}
}
return AccessDenied();
}
}`
This is my Startup
`services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
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"]))
};
});
services.AddAuthentication()
.AddGoogle(opts =>
{
opts.ClientId = "715561755180-h72ug8g5v4sfgcn150n8mjaq75oacmp8.apps.googleusercontent.com";
opts.ClientSecret = "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";
opts.SignInScheme = IdentityConstants.ExternalScheme;
});`
I have a regular jwt authentication with email and password works well with react,.net core and identity, but google login is my problem
I expect a guide or tips to help me move forward with the project

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")]

IdentityServer 4 - invaid_client error

I am new to Identity Server. I haven't configured it before. But I need it for a Project I am working on.
The API will be serving an Angular JS Client, iOS App and an Android App. We need to implement authentication and authorisation and custmer grant
Note: I am trying to configure Identity Server and my API in the same Web API project.
I have followed the documentation and configured Identity Server as the following:
In startup.cs, in ConfigureServices()
private readonly IConfiguration config;
private const string DEFAULT_CORS_POLICY = "localhost";
public Startup (IConfiguration config) => this.config = config;
public void ConfigureServices (IServiceCollection services) {
services.AddIdentityServer ()
.AddDeveloperSigningCredential ()
//.AddInMemoryApiResources(config.GetSection("ApiResources"))
.AddInMemoryApiResources (Config.GetApis ())
//.AddInMemoryClients(config.GetSection("Clients"))
.AddInMemoryClients (Config.GetClients ())
.AddInMemoryIdentityResources (Config.GetIdentityResources ())
//.AddInMemoryIdentityResources(config.GetSection("IdentityResources"))
.AddExtensionGrantValidator<WechatGrantValidator> ();
services.AddTransient<IUserCodeValidator, UserCodeValidator> ();
services.AddCors (options => {
options.AddPolicy (DEFAULT_CORS_POLICY, builder => {
builder.WithOrigins ("http://localhost:5202");
builder.AllowAnyHeader ();
builder.AllowAnyMethod ();
});
});
}
I implemented the interface IExtensionGrantValidator and register the extension grant
public class WechatGrantValidator : IExtensionGrantValidator {
private IUserCodeValidator validator;
public WechatGrantValidator (IUserCodeValidator validator) {
this.validator = validator;
}
public string GrantType => "wechat_grant";
public async Task ValidateAsync (ExtensionGrantValidationContext context) {
string userCode = context.Request.Raw.Get ("userCode");
var result = await validator.ValidateAsync (userCode);
if (result.IsError) {
context.Result = new GrantValidationResult (TokenRequestErrors.InvalidGrant);
return;
}
context.Result = new GrantValidationResult (result.UserId, GrantType);
return;
}
}
I have followed the documentation and configured client infos as the following
public static IEnumerable<Client> GetClients () {
return new Client[] {
new Client {
ClientId = "javascritpClient",
ClientName = "JavaScript Client",
AllowedGrantTypes = { "wechat_grant" },
AllowAccessTokensViaBrowser = true,
AllowedCorsOrigins = { "http://localhost:5202" },
AllowedScopes = { "api1" },
ClientSecrets = { new Secret ("secret".Sha256 ()) }
}
};
}
Now because I want to use it Angular JS, iOS and Android I want to just get the Access Token from the IdentityServer, and then use the Access Token for Authentication and Authorisation.
for this I am trying to access the /connect/token from a JS client
But I am getting an invalid_client error.
#Injectable()
export class OauthService {
private http: Http;
public constructor(http: Http) {
this.http = http;
}
public async getDiscoveryInfos(issuer: string): Promise<DiscoveryInfos> {
if (!issuer.endsWith('/')) {
issuer += '/';
}
issuer += '.well-known/openid-configuration';
return this.http.get(issuer).map(response => {
return response.json();
}).toPromise();
}
public async getToken(): Promise<any> {
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
const discovery = await this.getDiscoveryInfos('http://localhost:5200');
return this.http.post(discovery.token_endpoint, {
grant_type: 'wechat_grant',
userCode: 'userCodeAA',
client_id: 'javascritpClient',
client_secret: 'secret',
scope:'api1'
}, { headers: headers }).map(response => response.json()).toPromise();
}
}
http response infos
The server response "error":"invalid_client"
log infos
The error I get on the server side is 'No client identifier found':
1 - Why am I getting this error?
2 - As I need to get the Token programmatically in JS, I need to use /connect/token, am I correct on this? Am I on the correct path?
in ng2 use a method like bellow:
public Token(data: SigninModel): Observable<any> {
this.options = new RequestOptions({ headers: this.headers });
this.headers.append('Content-Type', 'application/x-www-form-urlencoded');
const url = this.urlBase + `connect/token`;
const param = new URLSearchParams();
param.set('grant_type', 'password');
param.set('client_Id', 'javascritpClient');
param.set('client_secret', 'secret');
param.set('scope', 'offline_access');
param.set('username', data.username);
param.set('password', data.password);
return this.http.post(url, `${param.toString()}`, this.options)
.map((response: Response) => {
return (response.json());
})
.catch(this.handleError);
}

Identity Server 4 Auto Login After Registration

I want the user to be auto logged after registration to the MVC app (email verification is not needed now), I have followed this sample for IdSrv3 and edited some parts:
http://benfoster.io/blog/identity-server-post-registration-sign-in
Here is my Setup:
MVC:
public class AuthController : Controller
{
[HttpGet]
[AllowAnonymous]
public async Task LogIn()
{
var properties = new AuthenticationProperties
{
RedirectUri = Url.Action("index", "home", HttpContext.Request.GetUri().Scheme)
};
await HttpContext.Authentication.ChallengeAsync(properties);
}
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
var returnUrl = $"http://localhost:5002/auth/login";
return Redirect($"http://localhost:5000/Account/Register?returnUrl={returnUrl}");
}
}
Identity Server 4 using ASp.Net Core Identity
[Authorize]
public class AccountController : Controller
{
//
// GET: /Account/Register
[HttpGet]
[AllowAnonymous]
public IActionResult Register(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var otac = user.GenerateOTAC(TimeSpan.FromMinutes(1));
await _userManager.UpdateAsync(user);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
if (string.IsNullOrEmpty(returnUrl))
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
else
{
return Redirect(returnUrl);
}
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
I could not implement the OTAC handshake as the sample shows becaus there is no User Service any more in IdSrv 4.
The scenario works as follows:
1 - User clicks 'Register' in MVC app whish redirects to the IdSrv Account->Register passing the MVC Auth->login as a return URI.
2 - After the user completed the registration in IdSrv another redirect executes to return back to the MVC Auth->Login.
3 - The MVC Auth->Login creates a challenge and since the user is already signed in in the registration process using the cookie, the authentication cookie gets merged (as per the IdSrv log), and there is no login screen appears and now the user is logged in and landed to the MVC app.
Does this setup have any secutity flaw?, is there any way I can implement the OTAC handshake in IdSrv4?
thanks,
In your case maybe this could work
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}

Google App Engine. Channel API. The provided token is invalid.

I've been trying to make an application for calling browser to browser using WebRTC. I wrote a simple servlet for creating channel:
PrefClubChannelServletServlet extends HttpServlet {
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String channelKey = req.getParameter("userid");
resp.addHeader("Access-Control-Allow-Origin", "*");
ChannelService channelService = ChannelServiceFactory.getChannelService();
String token = channelService.createChannel(channelKey);
resp.setContentType("text/plain");
resp.getWriter().println(token);
}
}
I've deployed it in Google App Engine.
In my web application I've got a page with Java script, similar to https://apprtc.appspot.com. In my code Caller calls prepare(1), and Callee – prepare(0).
function prepare(ini) {
initiator = ini;
card = document.getElementById("card");
localVideo = document.getElementById("localVideo");
miniVideo = document.getElementById("miniVideo");
remoteVideo = document.getElementById("remoteVideo");
resetStatus();
console.log("Try to get token");
getToken();
}
function getToken() {
var token;
if (initiator) {
var xhr = new XMLHttpRequest();
var url = 'http://pref-club.appspot.com/prefclubchannelservlet?userid=GGGG';
xhr.open('POST', url, true);
// Specify that the body of the request contains form data
xhr.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
xhr.send(url);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
token = xhr.responseText;
console.log("token = " + token);
document.getElementById("token").value = token;
openChannel(token);
doGetUserMedia();
}
};
} else {
token = document.getElementById("token").value;
console.log("token = " + token);
openChannel(token);
doGetUserMedia();
}
};
So, both Caller and Callee use the same token to open channel, and indeed they open channel, got media and made RTCPeerConnection
function openChannel(channelToken) {
console.log("Opening channel.");
var channel = new goog.appengine.Channel(channelToken);
var handler = {
'onopen': onChannelOpened,
'onmessage': onChannelMessage,
'onerror': onChannelError,
'onclose': onChannelClosed
};
socket = channel.open(handler);
}
The main problem is that event handler onChannelMessage doesn't work. I don't see any S->C: in console log. Callee doesn't see offer by Caller.
Then, I refreshed my servlet, redeployed it, and discovered I can't open channel at all. While Opening channel I'm getting Uncaught Error:
The provided token is invalid.

Resources