Problem
res = null in Effect
Everything seems working other than getting response from Effect. I can return identity from authcontroller and successfully saved when creating a user. Because I get null response I can't do futher steps this.store.dispatch(new UserCreated({ user: res }));. What did I miss?
http service
createUser(user: User): Observable<User> {
var userRoles = user.roles.map(x=>x.name);
const body = {
username: user.username,
password: user.password,
firstname: user.firstname,
lastname: user.lastname,
roles: userRoles,
email: user.email,
phonenumber:user.phonenumber,
};
const httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Type", "application/json");
return this.http
.post<User>(API_CREATE_USER_URL, body,{headers:httpHeaders});
}
Effect
#Effect()
createUser$ = this.actions$
.pipe(
ofType<UserOnServerCreated>(UserActionTypes.UserOnServerCreated),
mergeMap(( { payload } ) => {
debugger;
this.store.dispatch(this.showActionLoadingDistpatcher);
return this.auth.createUser(payload.user).pipe(
tap(res => {
debugger;
this.store.dispatch(new UserCreated({ user: res }));
})
);
}),
map(() => {
return this.hideActionLoadingDistpatcher;
}),
);
asp.net core Account controller
[HttpPost]
public async Task<IActionResult> Post(RegistrationViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<AppUser>(model);
//var userIdentity = new AppUser();
//userIdentity.Email = model.Email;
//userIdentity.UserName = model.UserName;
//var role = _roleManager.FindByNameAsync(model.RoleName).Result;
//await _userManager.AddToRoleAsync(userIdentity, role.Name);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
if (!result.Succeeded)
{
return new BadRequestObjectResult(Error.AddErrorsToModelState(result, ModelState));
}
await _userManager.AddToRolesAsync(userIdentity, model.Roles);
await _appDbContext.SaveChangesAsync();
return Ok(userIdentity);
}
The frontend part is correct. Looks like your backend doesn't return the created user.
I would recommend you to check the network activity in the developer tools when a request to API_CREATE_USER_URL has been sent.
Related
Trying to Authenticate using CookieAuthenticationDefaults.AuthenticationScheme with OpenIddict through angular client application but cookie not authenticated. does anyone know whats the issue
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginRequestDTO model)
{
var user = await _context.Users.Where(x => x.Username == model.Username).FirstOrDefaultAsync();
if(user == null)
{
return Ok(GenericResponse<bool>.Failure("Invalid Username or Password!", Models.Enums.ApiStatusCode.RecordNotFound));
}
else
{
if(user.Password != model.Password)
{
return Ok(GenericResponse<bool>.Failure("Invalid Username or Password!", Models.Enums.ApiStatusCode.RecordNotFound));
}
else
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, model.Username)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(new ClaimsPrincipal(claimsIdentity));
return Redirect(model.ReturnUrl);
}
}
}
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//options.LoginPath = "/login";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (context) =>
{
context.HttpContext.Response.Redirect("http://localhost:4200/login" + context.RedirectUri.Split("Login")[1]);
return Task.CompletedTask;
}
};
});
As part of my login endpoint I return a generated token using the 'generateToken' function. The payload contains an object of claims(user.id and user.role). However when I log in and check the returned token I do not see any of the claims; just the 'created' and 'expires' values.
Login endpoint
async function findUserById(req, res){
let { email, password } = req.body;
try {
const user = await db.query("SELECT * FROM user_account WHERE email = $1", [email]);
if(!user.rows.length){
return res.status(401).json("Invalid crendential")
}
const validPassword = await bcrypt.compareSync(password, user.rows[0].password)
if(!validPassword){
return res.status(401).json("Invalid credential");
}
const token = await generateToken(user);
res.status(200).json({ user, email: user.email, token})
} catch (error) {
res.status(500).json({error: error.message})
}
}
generateToken function
const jwt = require("jsonwebtoken");
const secret = require("../config/secrets");
function generateToken(user){
const payload = {
subject: user.id,
role: user.role
};
const options = {
expiresIn: "30d"
};
return jwt.sign(payload, secret.jwtSecret, options)
}
module.exports = {generateToken};
I am using core 3.1 to connect to the canvas API, this is part of my code..
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = "CanvasCookies";
config.DefaultSignInScheme = "CanvasCookies";
config.DefaultChallengeScheme = "CanvasLMS";
})
.AddCookie("CanvasCookies")
.AddOAuth("CanvasLMS", config =>
{
var canvas_domain = Configuration.GetValue<string>("Canvas:Domain");
var client_secret = Configuration.GetValue<string>("Canvas:Secret");
var client_id = Configuration.GetValue<string>("Canvas:Client_id");
config.ClientId = client_id;
config.ClientSecret = client_secret;
config.CallbackPath = new PathString("/oauth/callback");
//config.Scope.Add("google.com")
config.AuthorizationEndpoint = $"{canvas_domain}login/oauth2/auth";
config.TokenEndpoint = $"{canvas_domain}login/oauth2/token";
config.UserInformationEndpoint = $"{canvas_domain}api/v1/users//courses";
config.SaveTokens = true;
config.Events = new OAuthEvents()
{
OnCreatingTicket = context =>
{
var accessToken = context.AccessToken;
var base64payload = accessToken.Split('.')[1];
var bytes = Convert.FromBase64String(base64payload);
var jsonPayload = Encoding.UTF8.GetString(bytes);
var claims = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonPayload);
foreach(var claim in claims)
{
context.Identity.AddClaim(new Claim(claim.Key, claim.Value));
}
return Task.CompletedTask;
}
this is the controller
public class APICanvasController : Controller
{
...
[Authorize]
public async Task<IActionResult> Secret()
{
var serverResponse = await AccessTokenRefreshWrapper(
() => SecuredGetRequest("https://localhost:44388/secret/index"));
var apiResponse = await AccessTokenRefreshWrapper(
() => SecuredGetRequest("https://localhost:44388/secret/index"));
return View();
}
private async Task<HttpResponseMessage> SecuredGetRequest(string url)
{
var token = await HttpContext.GetTokenAsync("access_token");
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
return await client.GetAsync(url);
}
public async Task<HttpResponseMessage> AccessTokenRefreshWrapper(
Func<Task<HttpResponseMessage>> initialRequest)
{
var response = await initialRequest();
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await RefreshAccessToken();
response = await initialRequest();
}
return response;
}
private async Task RefreshAccessToken()
{
...
}
}
}
when I execute the code I obtain this error
Exception: The oauth state was missing or invalid.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
Any idea what I am doing wrong?
Thanks
CallbackPath is not supposed to refer to a controller, it refers to a unique path handled by the auth middleware. It will redirect back to your controller when it's done.
"/oauth/callback" should handle oauth authentication result as a json instead of page.
I'm fairly new to token based authentication and I have a problem of how to maintain login state after I login.
I want to create a SPA website for which I am using Knockoutjs for my front end and SammyJS for routing and changing the views.
After I login in and get the token I store it in localStorage and set the username into an observable which I am displaying.
My problem is that after I close the tab or browser and I go back to the site, the token is in the localStorage but I can't see the user logged in.
I want to maintain the login state until the token expires. My question is what should I do with the token from the localStorage when I enter the site in order to maintain the login state of that user?
Do I need to make something in the startup class or to check if that user exists in the DB?
Thanks in advance!
Here is my code:
StartupAuth.cs
[assembly: OwinStartup(typeof(EventHub.PL.WebUI.Startup))] namespace EventHub.PL.WebUI {
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get;private set; }
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public const string TokenEndpointPath = "/api/token";
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(TokenEndpointPath),
Provider = new ApplicationOAuthProvider(PublicClientId),
//AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
//app.UseOAuthBearerTokens( OAuthOptions );
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
AccountController.cs
[HttpPost]
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login(LoginUser model)
{
var request = HttpContext.Current.Request;
var tokenServiceUrl = request.Url.GetLeftPart(UriPartial.Authority) + request.ApplicationPath + "/api/Token";
using (var client = new HttpClient())
{
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", model.Email),
new KeyValuePair<string, string>("password", model.Password)
};
var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
var tokenServiceResponse = await client.PostAsync(tokenServiceUrl, requestParamsFormUrlEncoded);
var responseString = await tokenServiceResponse.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<TokenResponse>(responseString);
var responseCode = tokenServiceResponse.StatusCode;
if (responseCode == HttpStatusCode.OK)
{
RegisterUser user = userRepository.GetNameById(json.Id);
var data = new
{
status = "success",
json.access_token,
user.Lastname
};
return Json(data);
}
return Json(new { status = "failed" });
}
}
here is the KO part:
var LoginApp = function () {
var instance = this;
instance.mainViewModel = new MainViewModel();
instance.loginViewModel = new LoginViewModel();
instance.loginRepository = new LoginRepository();
instance.loginViewModel.signIn = function() {
$('.loader-header').show();
var postData = {
email: instance.loginViewModel.email(),
password: instance.loginViewModel.password
}
instance.loginRepository.SignIn(SignInSuccess, postData);
};
instance.SignInSuccess = function(response) {
if (response.status === 'success') {
instance.mainViewModel.username(response.Lastname);
instance.mainViewModel.isVisible(true);
var userData = {
token: response.access_token,
username: response.Lastname
};
localStorage.setItem('AuthorizationData', JSON.stringify(userData));
$('.loader-header').hide();
dialog.close();
} else {
$('.loader-header').hide();
}
};
instance.init = function () {
ko.applyBindings(instance.loginViewModel, document.getElementById("signin-form"));
ko.applyBindings(instance.mainViewModel, document.getElementById("main-wrapper"));
}
instance.init();
}
$(document).ready(function () {
var loginApp = LoginApp();
});
UPDATE
here is my routing also
var appRoot = root;
(function ($) {
var app = $.sammy('#page', function () {
this.get('#/home', function (context) {
document.title = 'Home - ' + title;
var url = getUrlFromHash(context.path);
loadView(url, new MainViewModel(), MainApp);
//context.load(url).swap();
});
this.get('#/about', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
this.get('#/manage', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
});
$(function () {
app.run('#/home');
});
})(jQuery);
function loadView(url, viewModel, callback) {
$.get(url, function (response) {
var $container = $('#page');
//var $view = $('#page').html(response);
$container.html(response);
callback();
});
}
function getUrlFromHash(hash) {
var url = hash.replace('#/', '');
if (url === appRoot)
url = 'home';
return url;
}
Right now all you're doing is storing the user's credentials in localStorage but not using them to perform authorization. One alternative is to use the Sammy.OAuth2 plugin (which you can find it here).
You can define a route to make the authentication like:
app.post("#/oauth/login", function(context) {
this.load('http://yourwebsite/login',
{
cache: false,
type: 'post',
data: {
email: $("input[name=email]").val(),
password: $("input[name=password]").val()
}
})
.then(function(content) {
if(content != false){
if(app.getAccessToken() == null){
app.setAccessToken(token());
}
}else{
app.trigger("oauth.denied");
return false;
}
});
});
In 'protected' routes you can check if the user is already logged in like this:
app.get("#/profile", function(context) {
if(app.getAccessToken() != null)
context.render('view/profile.template');
else
this.requireOAuth();
});
This examples will have to be modified to populate the token according to your scenario. Here's a complete tutorial on Sammy.Oath2.
I have some code in my Starup.cs
public void ConfigureOAuth(IAppBuilder app) {
OAuthAuthorizationServerOptions OAuthSerOptions = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
app.UseOAuthAuthorizationServer(OAuthSerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
and I have a login Action method which look like...
[HttpPost]
[AllowAnonymous]
public async Task<MyUser> Login(LoginModel model)
{
if (!ModelState.IsValid)
{
return null;
}
var user = new MyUser()
{
UserName = model.UserName,
PasswordHash = model.Password
};
var result = await UserManager.FindAsync(model.UserName, model.Password);
return result;
}
Using Postman when I make a call to the above method I can see that get the body as
{
"$id": "1",
"Claims": [],
"Logins": [],
"Roles": [],
"FirstName": null,
"Id": "001fbdf7-a199-47df-ba34-b181886d084f",
"UserName": "ABC",
"PasswordHash": "AF7iQeV/11nrQ8LUXriGKw8eHEx1DDOnQrcANhndLto+FxDxH1xzPJqETXiII8HzOQ==",
"SecurityStamp": "b1a883d1-bbcc-4b67-a3fb-644e13c0cfd7"
}
Edits :
Within my ApplicationOAuthProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (UserManager<MyUser> userManager = _userManagerFactory())
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("UserName", context.UserName));
identity.AddClaim(new Claim("Password", context.Password));
identity.AddClaim(new Claim("token", context.Ticket.ToString()));
identity.AddClaim(new Claim("ClientId", context.ClientId));
context.Validated(identity);
}
}
I am unable to find the token though the Startup.cs contains the code for the same. I am new to this concept plz let me know if I am wrong somewhere. Thanks
IMHO this work for me
ApplicationOAuthProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
I just need to invoke the mysite/token pass the credentials in the body it will return the token and user details.
Update
Invoking from js
$("#btnLogin").click(function () {
var user = new Object();
user.username = $('#txtUserName').val();
user.password = $('#txtPassword').val();
user.grant_type = 'password';
$.ajax({
url: 'http://localhost:61660/token',
type: 'POST',
dataType: 'json',
contentType: "application/json",
data: user,
success: function (data, textStatus, xhr) {
debugger;
console.log(data);
window.localStorage.setItem('access_token', data.access_token);
window.location.href = 'http://localhost:61660/UserHome/Index';
},
error: function (xhr, textStatus, errorThrown) {
alert("Invalid username/password")
console.log('Error in Operation');
}
});
});
});