I am making for the first time app with authorization and I have a bit of a problem here. I am using angular asp.net default auth, my context inheritance looks like this:
public class CoffeeContext : IdentityDbContext<User, UserRole, int>
UserRole is an empty class that inheritance from IdentityRole < int >. User is also empty class that inheritance from IdentityUser < int >.
This is how my UsersControllerClass looks like:
[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
private readonly SignInManager<User> _signInManager;
private readonly UserManager<User> _userManager;
public UsersController(SignInManager<User> signInManager, UserManager<User> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(UserDto userDto)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(userDto.Email, userDto.Password, false, false);
if (result.Succeeded)
{
return Ok();
}
else
{
return BadRequest();
}
}
return Ok();
}
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register(UserDto userDto)
{
if (ModelState.IsValid)
{
var user = new User() {UserName = userDto.Email, Email = userDto.Email};
var result = await _userManager.CreateAsync(user, userDto.Password);
if (result.Succeeded)
{
return Ok();
}
}
return BadRequest();
}
[HttpGet]
[Route("Logout")]
public async Task<IActionResult> Logout()
{
if (ModelState.IsValid)
{
await _signInManager.SignOutAsync();
}
return Ok();
}
}
UserDto has 2 properties: string Email, string Password. I have a few classes that have a userId property. For example recipe:
[ApiController]
[Route("Recipes")]
public class RecipesController : ControllerBase
{
private readonly IRecipeService _recipeService;
private readonly Microsoft.AspNetCore.Identity.UserManager<User> _userManager;
public RecipesController(IRecipeService recipeService, Microsoft.AspNetCore.Identity.UserManager<User> userManager)
{
_recipeService = recipeService;
_userManager = userManager;
}
[HttpPost]
public async Task<IActionResult> CreateRecipe(RecipeDto recipeDto)
{
var userId = IdentityExtensions.GetUserId(User.Identity);
recipeDto.UserId = int.Parse(userId);
await _recipeService.CreateRecipe(recipeDto);
return CreatedAtAction(nameof(GetRecipeById), new { Id = recipeDto.Id }, recipeDto);
}
As you can see I am getting userId by IdentityExtension.GetUserId();
When I am testing this method with swagger (first using login method from user controller and then CreateRecipe) everything works fine, it gets right id of currently login user. The problem is when I try to use that method via frontend with angular. This is how my login module looks like:
export class LoginComponent implements OnInit {
userForm! : FormGroup;
user! : LoginModel;
subbmited = false;
constructor(private formBuilder: FormBuilder, private userService: UserService) { }
ngOnInit(){
this.userForm = this.formBuilder.group({
email: ['', Validators.required],
password: ['', Validators.required]
})
}
get Email(){
return this.userForm.get('email');
}
get Password(){
return this.userForm.get('password');
}
onSubmit(){
this.user = this.userForm.value;
this.userService.LogIn(this.user).subscribe(res => { console.log(res)});
}
userService, values passed by loginComponent are correct (register method is working fine):
export class UserService {
readonly ApiUrl = "https://localhost:44331/users"
constructor(private http: HttpClient) { }
LogIn(loginModel: LoginModel){
return this.http.post(this.ApiUrl '/Login', loginModel, this.generateHeaders());
}
Register(loginModel: RegisterModel){
return this.http.post(this.ApiUrl '/Register', loginModel, this.generateHeaders());
}
private generateHeaders = () => {
return {
headers: new HttpHeaders({
"Content-Type": "application/json",
}),
};
};
}
And this is how I add recipe:
addRecipe(){
var val = {
id:this.RecipeId,
name:this.RecipeName,
recipeBody:this.RecipeBody,
userId: 0,
intendedUse:this.RecipeIntentedUse,
coffeeId:this.RecipeCoffeeId
};
this.service.createRecipe(val).subscribe(res=>{
alert(res.toString());
});
}
Values from inputs are correct, and I set userId to 0 since it is gonna be replaced anyway in the backend (or at least I think it should). In the end I am getting Http status: 500, Value can not be null. When I check with debbuging how userId value looks like in backend:
As you can see currently logged in user have an Id of null which I suppose means that there is no logged in users. How can I fix it?
Edit: recipe.component.ts:
import { Component, OnInit } from '@angular/core';
import { SharedService } from '../shared/shared.service';
@Component({
selector: 'app-recipe',
templateUrl: './recipe.component.html',
styleUrls: ['./recipe.component.css']
})
export class RecipeComponent implements OnInit {
ModalTitle:string="";
recipe: any;
ActiveAddEdditRecipe:boolean=false;
constructor(private service:SharedService) { }
RecipeList: any = [];
ngOnInit(): void {
this.refreshRecipeList();
}
refreshRecipeList(){
this.service.getRecipes().subscribe(data => {
this.RecipeList=data;
})
}
addClick(){
this.recipe={
id:0,
name:"",
recipeBody:"",
userId:"",
intendedUse:"",
coffeeId:""
}
this.ModalTitle="Add recipe";
this.ActiveAddEdditRecipe=true;
}
editClick(recipeEdit: any){
this.recipe=recipeEdit;
this.ModalTitle="Edit recipe";
this.ActiveAddEdditRecipe=true;
}
closeClick(){
this.ActiveAddEdditRecipe=false;
this.refreshRecipeList();
}
deleteClick(recipeDelete: any){
this.service.deleteRecipe(recipeDelete).subscribe(data => this.refreshRecipeList());
}
}
html:
<button type="button" class="btn btn-primary float-right m-2" data-bs-toggle="modal" data-bs-target="#exampleModal"
(click)="addClick()"
data-backdrop="static"
data-keyboard="false">
Add Coffee
</button>
<div class="modal" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{{ModalTitle}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
(click)="closeClick()" (click)="refreshRecipeList()">
</button>
</div>
<div class="modal-body">
<app-add-edit-recipe [recipe]="recipe" *ngIf="ActiveAddEdditRecipe"></app-add-edit-recipe>
</div>
</div>
</div>
</div>
<div class="wrapper">
<header>
<h1>Recipe</h1>
</header>
<section class="columns" *ngFor="let recipe of RecipeList">
<div class="column">
<h2>{{recipe.name}}</h2>
<p>{{recipe.recipeBody}}</p>
</div>
</section>
</div>
And AddEditRecipeComponent
Component({
selector: 'app-add-edit-recipe',
templateUrl: './add-edit-recipe.component.html',
styleUrls: ['./add-edit-recipe.component.css']
})
export class AddEditRecipeComponent implements OnInit {
@Input() recipe: any;
RecipeId!: number ;
RecipeName: string="";
RecipeBody : string="";
RecipeUserId : number = 0;
RecipeIntentedUse: string ="";
RecipeCoffeeId: string ="";
CoffeeList: any[] = [];
constructor(private service : SharedService) { }
ngOnInit(): void {
this.RecipeId = this.recipe.id;
this.RecipeName = this.recipe.name;
this.RecipeBody = this.recipe.recipeBody;
this.RecipeUserId = this.recipe.userId;
this.RecipeIntentedUse = this.recipe.intendedUse;
this.RecipeCoffeeId = this.recipe.coffeeId;
this.getCoffeesList();
}
addRecipe(){
var val = {
id:this.RecipeId,
name:this.RecipeName,
recipeBody:this.RecipeBody,
userId: 1,
intendedUse:this.RecipeIntentedUse,
coffeeId:this.RecipeCoffeeId
};
this.service.createRecipe(val).subscribe(res=>{
alert(res.toString());
});
}
updateRecipe(){
var val = {
id:this.RecipeId,
name:this.RecipeName,
recipeBody:this.RecipeBody,
userId:this.RecipeUserId,
intendedUse:this.RecipeIntentedUse,
coffeeId:this.RecipeCoffeeId
};
this.service.updateRecipe(val).subscribe(res=>{
alert(res.toString());
});
}
getCoffeesList(){
this.service.getCoffees().subscribe(data => {
this.CoffeeList = data;
})
}
}
<div class ="col-sm-10">
<input type="text" class="form-control" [(ngModel)]="RecipeName" placeholder="Enter recipe name">
<input type="text" class="form-control" [(ngModel)]="RecipeBody" placeholder="Enter recipe text">
<select type="text" class="form-control" [(ngModel)]="RecipeIntentedUse">
<option value="" disabled selected>Intended Use</option>
<option value="Espresso">Espresso</option>
<option value="Aeropress">Aeropress</option>
<option value="FrenchPress">FrenchPress</option>
<option value="Chemex">Chemex</option>
<option value="Dripper">MediDripperum</option>
<option value="MokaPot">MokaPot</option>
</select>
<select type="text" class="form-control" [(ngModel)]="RecipeCoffeeId">
<option value="" disabled selected>Select coffee</option>
<option *ngFor="let coffee of CoffeeList"
[value]="coffee.id" >{{coffee.name}}</option>
</select>
</div>
Add
Update
CodePudding user response:
Your logIn()
function is incorrectly implemented.
It is not sending any authentication headers that is why it is not working.
I fail to understand what is going on with your code, but assuming your backend works perfectly, this is how I would do it:
I think what you should do is to create an Auth Interceptor and setting all Http requests on your Angular app to include your authorization headers.
Try this (Assuming you have used BasicAuth):
On your login.ts
module
export class LoginComponent implements OnInit {
userForm! : FormGroup;
email : any;
password: any;
encodedValues: any;
subbmited = false;
constructor(private formBuilder: FormBuilder, private userService: UserService) { }
ngOnInit(){
this.userForm = this.formBuilder.group({
email: ['', Validators.email],
password: ['', Validators.required]
})
}
getEmail(event:any){
this.email = event.target.value;
}
getPassword(event:any){
this.password = event.target.value;
}
onSubmit(){
this.http.post("YOUR API ENDPOINT", this.userForm.getRawValue()).subscribe((res:any)=>{
this.encodedValues = btoa(this.email ":" this.password),
sessionStorage.setItem("encodedValues", this.encodedValues)
})
on your login.html
file, change your inputs to include the event changes:
<input type="text" formControlName="email" (change)="getEmail($event)">
<input type="text" formControlName="password" (change)="getPassword($event)">
That will store your authentication details on your browser storage, and will be passed as a header by AuthInterceptor
Generate an Authentication interceptor:
ng g interceptor AuthInterceptor
On your AuthInterceptor
file, add this to retreive your auth details and pass them as a header on every :
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const token = localStorage.getItem("encodedValues")
request = request.clone({
setHeaders: {
Authorization: `Basic ${token}`
}
});
return next.handle(request);
}
}
export const authInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
];
Finally, on adding a recipe, kindly share the whole .ts
so I can have something to work with
EDIT
Your service.ts
file and RecipeComponent
file looks correct.
If you added your the AuthInterceptor as stated above, then, go to your app.module.ts and add this on your providers.
providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ],
That will set the headers on all HTTP requests
That should work