401 (Unauthorized) request on Angular 7, .netcore application - c#

Iam getting a 401 unauthorized error while authorizing an user to consume an api on .net core and angular 7 application.
My angular service has a function :-
getUserProfile() {
var tokenHeader = new HttpHeaders({'Authorization':'Bearer ' + localStorage.getItem('token')});
console.log(tokenHeader)
return this.http.get('/api/ApplicationUser/UserProfile', { headers: tokenHeader});
}
on tokenHeader I am sending the user jwt token.
My api is
[HttpGet]
[Authorize]
[Route("UserProfile")]
//'api/userProfile'
public async Task<Object> GetUserProfile()
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
return new { user.fullName, user.Email, user.UserName };
}
I have tried some answers from other questions but nothing helps.
Any helps appreciated.

Your code should be like this
const httpOptions = {
headers: new HttpHeaders({
'Authorization': `Bearer ${localStorage.getItem('token')}`
})
};
return this.http.get('/api/ApplicationUser/UserProfile', httpOptions);
Also make sure you have this line in your controller
[Authorize(AuthenticationSchemes = "Bearer")]

Can you share your request from network tab.
Also I recommend to use interceptors for make it global
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(public authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.getJwtToken()) {
request = this.addToken(request, this.authService.getJwtToken());
}
return next.handle(request).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else {
return throwError(error);
}
}));
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
'Authorization': `Bearer ${token}`
}
});
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return this.authService.refreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.jwt);
return next.handle(this.addToken(request, token.jwt));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(jwt => {
return next.handle(this.addToken(request, jwt));
}));
}
}
}
Full example https://github.com/bartosz-io/jwt-auth-angular/blob/master/src/app/auth/token.interceptor.ts

Related

OData ASPNetCore CamelCase

I am struggling to find documentation on camelCase feature for .net 6 vs 8.0.6 of Microsoft.AspNetCore.OData
https://github.com/OData/AspNetCoreOData/issues/13#issuecomment-1013384492
The issue is when you query directly it's fine.
But when you use any functionality it breaks
Any ideas?
Code for Config in Program.cs
builder.Services.AddControllersWithViews().AddOData(options =>
{
options.Select().Filter().Expand().Count().SetMaxTop(100).OrderBy();
});
Code for Endpoint
[HttpGet]
[EnableQuery()]
public async Task<IEnumerable<Warehouse>> Warehouses()
{
return _context.Warehouses;
}
Tired
static IEdmModel GetModel()
{
var builder1 = new ODataConventionModelBuilder();
builder1.EnableLowerCamelCase();
builder1.EntitySet<Warehouse>("warehouses");
builder1.EntitySet<Company>("companies");
return builder1.GetEdmModel();
}
builder.Services.AddControllersWithViews().AddOData(options =>
{
options.Select().Filter().Expand().Count().SetMaxTop(100).OrderBy();
options.AddRouteComponents(GetModel());
});
Until this is fix here's what I did with typescript/javascript to transform the response so that it can be mapped to swagger open api objects
return this.httpClient.request<any[]>('get', `${window.origin}/odata/${path}/?${odataQuery}`,
{
headers: this.headers
}
).pipe(map((things) => {
return things.map((thing) => {
return this.util.objectToCamel(thing);
});
}));
objectToCamel(thing: any): any{
const obj: any = {};
if (!thing)
return null;
Object.keys(thing).forEach((key) => {
if (Array.isArray(thing[key])) {
thing[key] = (<any[]>thing[key]).map((thx) => {
return this.objectToCamel(thx);
});
} else if (typeof thing[key] === "object")
thing[key] = this.objectToCamel(thing[key])
obj[key.substr(0, 1).toLowerCase() + key.substr(1)] = thing[key];
});
return obj;
}

How to return a value instead of a status if request is not authorised?

[authorise]
public string Get()
{
return "value1";
}
if I am not authorised it will return a status of 401 not authorised.
can it return a value such as json "{status:false,code:"401"}". ?
According to your description, I suggest you could try to use custommiddleware to achieve your requirement.
You could captured the 401 error in middleware and then rewrite the response body to {status:false,code:"401"}
More details, you could add below codes into Configure method above the app.UseAuthentication();:
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
await context.Response.WriteAsync("{status:false,code:'401'}");
}
});
Result:
You can create a custom authorize attribute using IAsyncAuthorizationFilter.
public class CustomAuthorizeFilter : IAsyncAuthorizationFilter
{
public AuthorizationPolicy Policy { get; }
public CustomAuthorizeFilter(AuthorizationPolicy policy)
{
Policy = policy ?? throw new ArgumentNullException(nameof(policy));
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Allow Anonymous skips all authorization
if (context.Filters.Any(item =&gt; item is IAllowAnonymousFilter))
{
return;
}
var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService&lt;IPolicyEvaluator&gt;();
var authenticateResult = await policyEvaluator.AuthenticateAsync(Policy, context.HttpContext);
var authorizeResult = await policyEvaluator.AuthorizeAsync(Policy, authenticateResult, context.HttpContext, context);
if (authorizeResult.Challenged)
{
// Return custom 401 result
context.Result = new CustomUnauthorizedResult("Authorization failed.");
}
else if (authorizeResult.Forbidden)
{
// Return default 403 result
context.Result = new ForbidResult(Policy.AuthenticationSchemes.ToArray());
}
}
}
public class CustomUnauthorizedResult : JsonResult
{
public CustomUnauthorizedResult(string message)
: base(new CustomError(message))
{
StatusCode = StatusCodes.Status401Unauthorized;
}
}
public class CustomError
{
public string Error { get; }
public CustomError(string message)
{
Error = message;
}
}
The code in this article does exactly what you want. click here
can it return a value such as json "{status:false,code:"401"}". ?
Sure, you can.
[ApiController]
[Produces("application/json")]
public class TestController : ControllerBase
{
public IActionResult Get()
{
if (User.Identity.IsAuthenticated)
{
return new OkObjectResult(new { status: true, code: 200 });
}
return new OkObjectResult(new { status: false, code: 401 });
}
}
But notice that, the request will return with the real status code 200 (OK)
You can also use UnauthorizedObjectResult like #vivek's comment:
return new UnauthorizedObjectResult(new { status: false, code: 401 });
You can return the below if using Asp.Net Core 3.1, It returns UnauthorizedObjectResult.
return Unauthorized(new { status: false, code: 401 });

How to confirm email using Angular and ASP.NET Core 3.1

I am using Angular 9 (localhost:4200) and WebAPI with dotnet Core 3.1 (localhost:5000). During user registration I pass user data from Client to Server and then in Register method I send him an email to verify. After user clicks the link it redirects him to localhost:5000 to VerifyEmail method.
How to create a link in Registration function so the code could hit the client function first and then confirm verification on server?
Or there is better way to confirm email with Angular/dotnet core WebApi?
Client Side:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { User } from '../_models/user';
import { environment } from '../../environments/environment';
#Injectable({
providedIn: 'root'
})
export class AuthService {
baseUrl = environment.apiUrl + 'auth/';
constructor(private http: HttpClient) {}
register(user: User) {
return this.http.post(this.baseUrl + 'register', user);
}
}
verifyEmail(data: any) { // not used
return this.http.post(this.baseUrl + 'VerifyEmail', data);
}
Server Side:
[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
{
var code = string.Empty;
var userToCreate = _mapper.Map<User>(userForRegisterDto);
try
{
var userExists = await _userManager.FindByNameAsync(userToCreate.UserName);
if (userExists == null)
{
var result = await _userManager.CreateAsync(userToCreate, userForRegisterDto.Password);
code = await _userManager.GenerateEmailConfirmationTokenAsync(userToCreate);
if (result.Succeeded)
{
//some code
}
}
else
{
var emailConfirmed = await _userManager.IsEmailConfirmedAsync(userExists);
code = await _userManager.GenerateEmailConfirmationTokenAsync(userExists);
if (emailConfirmed)
{
return Ok();
}
}
var link = Url.Action(nameof(VerifyEmail), "Auth", new { userId = userToCreate.Id, code }, Request.Scheme, Request.Host.ToString());
await _emailService.SendAsync("test#test.com", "email verify", $"Verify Email", true);
}
catch (Exception ex)
{
throw;
}
return Ok();
}
public async Task<IActionResult> VerifyEmail(string userId, string code)
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(code))
{
return BadRequest();
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return BadRequest();
}
if (user.EmailConfirmed)
{
//return Ok();
}
var result = await _userManager.ConfirmEmailAsync(user, code);
if (result.Succeeded)
{
return Ok();
}
return BadRequest();
}
Thank you in advance for any help and your time!
I'd create a new angular component that resolves to /verify-email route and from there called AuthService
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'email-verification',
template: '<h1>{{status}}</h1>',
styleUrls: [ './email-verification.component.css' ]
})
export class EmailVerificationComponent implements OnInit {
status: string = "Verifying...";
constructor(private authService: AuthService){}
ngOnInit() {
this.authService.verifyEmail().subscribe(_ => this.status = "Email verified!");
//redirect to another component
}
}

Angular 6 Post Request to .NET Core API

I am working with angular 6 trying to send a post request using httpclient , but always receive null body on the server side.
save( rules:RuleModel[]){
let _headers: HttpHeaders = new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8'
});
return this._httpClient.post(AppConfig.BaseUrl,JSON.stringify(rules), {headers:_headers} ); }
and API function
[HttpPost]
public List<Rule> AddTemplateTextRules( [FromBody]Rule[] Rules)
{
try
{
return RuleManager.AddRule(Rules);
}
catch (Exception e)
{
return null;
}
return null;
}
To make a post request in Angular 6 with standard practice you need to do followings:
In the service class:
import {throwError, Observable } from 'rxjs';
import {catchError} from 'rxjs/operators';
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpErrorResponse } from '#angular/common/http';
import { Rule } from 'path';
#Injectable()
export class RuleService {
constructor(private httpClient: HttpClient) { }
private baseUrl = window.location.origin + '/api/Rule/';
createTemplateTextRules(rules: Rules[]): Observable<boolean> {
const body = JSON.stringify(rules);
const headerOptions = new HttpHeaders({ 'Content-Type': 'application/json' });
return this.httpClient.post<boolean>(this.baseUrl + 'AddTemplateTextRules', body, {
headers: headerOptions
}).pipe(catchError(this.handleError.bind(this));
}
handleError(errorResponse: HttpErrorResponse) {
if (errorResponse.error instanceof ErrorEvent) {
console.error('Client Side Error :', errorResponse.error.message);
} else {
console.error('Server Side Error :', errorResponse);
}
// return an observable with a meaningful error message to the end user
return throwError('There is a problem with the service.We are notified &
working on it.Please try again later.');
}
}
In the Component:
export class RuleComponent implements OnInit {
constructor(private ruleService: RuleService) { }
createTemplateTextRules(): void {
this.ruleService.createTemplateTextRules(rules).subscribe((creationStatus) => {
// Do necessary staff with creation status
}, (error) => {
// Handle the error here
});
}
}
Then in the ASP.NET Core API Controller:
[Produces("application/json")]
[Route("api/Rule/[action]")]
public class RuleController : Controller
{
[HttpPost]
public Task<IActionResult> AddTemplateTextRules( [FromBody]Rule[] Rules)
{
try
{
return RuleManager.AddRule(Rules);
}
catch (Exception e)
{
return false;
}
return Json(true);
}
}
Hope it will help you.
With the latest RxJS(Angular 14) here is the way:
Service
Login(phone:string,password:string)
{
let _headers: HttpHeaders = new HttpHeaders({
'accept': 'application/json'
});
return this.http.post(this.url,{username,password},{headers:_headers})
.pipe(map(response=>response));
}
Component
async Login(phone:string,password:string)
{
let token$ = this.authService.Login(phone,password);
let token = await lastValueFrom(token$);
}
Since I was returning just text and not Json from the API, this was my code to handle text response type in the Service. If you're getting a response parse error, explicitly defining the responseType will help since Json is default.
Login(phone:string,password:string)
{
let _headers: HttpHeaders = new HttpHeaders({
'accept': 'text/plain'
});
return this.http.post(this.url+'security/login?phone='+phone+'&password='+password,null,{headers:_headers,responseType:'text'})
.pipe(map(response=>response));
}

External authentication with Azure Active Directory B2C

Currently we are exploring the use of AspNetBoilerPlate (.NETCore and Agular) and one of the biggest pieces we need to be able to use is Azure Active Directory B2C. After trying various methods to get the login page to redirect over to the Azure B2C instance I have had no luck. My goal it so not need to override much if any of the base ABP code in order to accomplish this. In addition, I have what is recommended in the documentation with no success.
https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management?searchKey=authentication#external-authentication
Sorry here are the pieces of code that I have with certain details removed:
Within the Portal.Web.Core project - Authentication I created a new class inheriting from ExternalAuthProviderApiBase and just hard-coded some values for the user. I still need to figure out how to get these from Azure AD B2C.
public class AzureActiveDirectoryB2CAuthProvider : ExternalAuthProviderApiBase
{
public const string Name = "AzureB2C";
public AzureActiveDirectoryB2CAuthProvider()
{
}
public override Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
{
ExternalAuthUserInfo user = new ExternalAuthUserInfo();
user.EmailAddress = "admin#aol.com";
user.Name = "Administrator";
user.Provider = this.ProviderInfo.Name;
user.ProviderKey = "12345";
user.Surname = "Cool Dude";
return Task.FromResult(user);
}
}
In the Portal.Web.Host --> Startup.cs I register the ExternalAuthProvider in the Configure method just after the app.UserAuthentication
var externalAuthConfiguration = app.ApplicationServices.GetRequiredService<ExternalAuthConfiguration>();
externalAuthConfiguration.Providers.Add(
new ExternalLoginProviderInfo(
AzureActiveDirectoryB2CAuthProvider.Name, string.Empty, string.Empty,
typeof(AzureActiveDirectoryB2CAuthProvider)
)
);
Now on the Angular2 side, I have added a reference to the MSAL.js library created by Microsoft to help handle the authentication.
Within the auth-route-guard.ts file I changed the canActivate method to call the login service which will try to determine if the user is already authenticated and if not redirect them to the Microsoft login page.
if (!this._sessionService.user) {
//this._router.navigate(['/account/login']);
this._loginService.updateUser();
return false;
}
Below you will see the code from the modified login.service.ts file minus the Azure AD B2C details.
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { TokenAuthServiceProxy, AuthenticateModel, AuthenticateResultModel, ExternalLoginProviderInfoModel, ExternalAuthenticateModel, ExternalAuthenticateResultModel } fro`enter code here`m '#shared/service-proxies/service-proxies';
import { UrlHelper } from '#shared/helpers/UrlHelper';
import { AppConsts } from '#shared/AppConsts';
import { MessageService } from '#abp/message/message.service';
import { LogService } from '#abp/log/log.service';
import { TokenService } from '#abp/auth/token.service';
import { UtilsService } from '#abp/utils/utils.service';
declare var Msal: any;
#Injectable()
export class LoginService {
static readonly twoFactorRememberClientTokenName = 'TwoFactorRememberClientToken';
private clientApplication: any;
authenticateModel: AuthenticateModel;
authenticateResult: AuthenticateResultModel;
externalAuthenticateModel: ExternalAuthenticateModel;
externalAuthenticateResult: ExternalAuthenticateResultModel;
rememberMe: boolean;
B2CTodoAccessTokenKey = "msal.idtoken";
tenantConfig = {
tenant: "",
clientID: '',
signUpSignInPolicy: "",
b2cScopes: ["openid"]
};
// Configure the authority for Azure AD B2C
authority = "https://login.microsoftonline.com/tfp/" + this.tenantConfig.tenant + "/" + this.tenantConfig.signUpSignInPolicy;
/*
* B2C SignIn SignUp Policy Configuration
*/
//clientApplication = new Msal.UserAgentApplication(
// this.tenantConfig.clientID, this.authority,
// function (errorDesc: any, token: any, error: any, tokenType: any) {
// // Called after loginRedirect or acquireTokenPopup
// }
//);
constructor(
private _tokenAuthService: TokenAuthServiceProxy,
private _router: Router,
private _utilsService: UtilsService,
private _messageService: MessageService,
private _tokenService: TokenService,
private _logService: LogService,
) {
this.clear();
this.clientApplication =
new Msal.UserAgentApplication(
this.tenantConfig.clientID,
this.authority,
this.authCallback);
}
authenticate(finallyCallback?: () => void): void {
finallyCallback = finallyCallback || (() => { });
var model = new ExternalAuthenticateModel;
model.authProvider = "AzureB2C";
model.providerAccessCode = this.getAccessToken();
model.providerKey = "12345";
this._tokenAuthService
.externalAuthenticate(model)
.finally(finallyCallback)
.subscribe((result: ExternalAuthenticateResultModel) => {
this.processExternalAuthenticateResult(result);
});
}
private processAuthenticateResult(authenticateResult: AuthenticateResultModel) {
this.authenticateResult = authenticateResult;
if (authenticateResult.accessToken) {
//Successfully logged in
this.login(authenticateResult.accessToken, authenticateResult.encryptedAccessToken, authenticateResult.expireInSeconds, this.rememberMe);
} else {
//Unexpected result!
this._logService.warn('Unexpected authenticateResult!');
this._router.navigate(['account/login']);
}
}
private processExternalAuthenticateResult(externalAuthenticateResult: ExternalAuthenticateResultModel) {
this.externalAuthenticateResult = externalAuthenticateResult;
alert(externalAuthenticateResult.accessToken);
if (externalAuthenticateResult.accessToken) {
//Successfully logged in
this.login(externalAuthenticateResult.accessToken, externalAuthenticateResult.encryptedAccessToken, externalAuthenticateResult.expireInSeconds, this.rememberMe);
} else {
//Unexpected result!
this._logService.warn('Unexpected externalAuthenticateResult!');
}
}
private login(accessToken: string, encryptedAccessToken: string, expireInSeconds: number, rememberMe?: boolean): void {
var tokenExpireDate = rememberMe ? (new Date(new Date().getTime() + 1000 * expireInSeconds)) : undefined;
this._tokenService.setToken(
accessToken,
tokenExpireDate
);
this._utilsService.setCookieValue(
AppConsts.authorization.encrptedAuthTokenName,
encryptedAccessToken,
tokenExpireDate,
abp.appPath
);
var initialUrl = UrlHelper.initialUrl;
if (initialUrl.indexOf('/login') > 0) {
initialUrl = AppConsts.appBaseUrl;
}
location.href = initialUrl;
}
private clear(): void {
this.authenticateModel = new AuthenticateModel();
this.authenticateModel.rememberClient = false;
this.authenticateResult = null;
this.rememberMe = false;
}
public AzureB2Clogin(): void {
this.isOnline();
this.clientApplication.loginRedirect(this.tenantConfig.b2cScopes);
}
saveAccessTokenToCache(accessToken: string): void {
sessionStorage.setItem(this.B2CTodoAccessTokenKey, accessToken);
};
public isOnline(): boolean {
return this.clientApplication.getUser() != null;
};
public getAccessToken(): string {
alert('AccessToken: ' + sessionStorage.getItem(this.B2CTodoAccessTokenKey));
return sessionStorage.getItem(this.B2CTodoAccessTokenKey);
}
public updateUser(): void {
if (this.isOnline()) {
this.authenticate();
}
else {
this.AzureB2Clogin();
}
}
public getAuthenticationToken(): Promise<string> {
return this.clientApplication.acquireTokenSilent(this.tenantConfig.b2cScopes)
.then(token => {
alert("Got silent access token: " + token);
return token;
}).catch(error => {
alert("Could not silently retrieve token from storage." + error);
return this.clientApplication.acquireTokenPopup(this.tenantConfig.b2cScopes)
.then(token => {
alert("Got popup access token: "+ token);
return token;
}).catch(error => {
alert("Could not retrieve token from popup." + error);
this.clientApplication.acquireTokenRedirect(this.tenantConfig.b2cScopes);
return Promise.resolve("");
});
});
}
private authCallback(errorDesc: any, token: any, error: any, tokenType: any) {
if (token) {
alert("Id token: " + token);
}
else {
alert(error + ":" + errorDesc);
}
this.getAuthenticationToken();
}
}
Finally index.html has the following script references added to it-->
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/0.1.3/js/msal.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>

Categories