How to return a reponse in ASP.NET Core 6 Web API JWT authentication when statuscode is 401 - jwt

I developed an ASP.NET Core 6 Web API, and I'm using JWT.
I try to return a response when status code = 401, but I can not figure out.
I want to return a response as shown here:
response.ResultCode = 401;
response.ResultMessage = "Invalid token, please call Login() method."
Could you please help me?

Since you are using JWT bearer authentication, you can hook a handler to the JwtBearerEvents.OnChallenge callback like below:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//other options....
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.OnStarting(async () =>
{
// Write to the response in any way you wish
await context.Response.WriteAsync("Invalid token, please call Login() method.");
});
return Task.CompletedTask;
}
};
});

I solved my problem like below.
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
var result = JsonConvert.SerializeObject(new
{
ResultCode = "2",
Message = "Invalid Token"
});
return context.Response.WriteAsync(result);
},
};

Related

ITokenAcquisition.GetAccessTokenForUserAsync throws NullReferenceException

In the Startup.cs of web api app, I have the following code. When this runs it throws NullReferenceException at GetAccessTokenForUserAsync. Any idea what I am missing?
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Events = new JwtBearerEvents()
{
OnTokenValidated = async ctx =>
{
var tokenAcquisition = ctx.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }, user: ctx.Principal);
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}));
// Get user information from Graph
var groups = await graphClient.Me.CheckMemberGroups(new[] { Constants.GroupId })
.Request()
.PostAsync();
}
};
});
services
.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddMicrosoftIdentityWebApi(this.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph()
.AddInMemoryTokenCaches();
NullReferenceException:
Update:
I Just figured out that the issue is because I am missing the authenticationScheme: JwtBearerDefaults.AuthenticationScheme parameter value for the GetAccessTokenForUserAsync method call but now I am getting this exception message ""IDW10104: Both client secret and client certificate cannot be null or whitespace, and only ONE must be included in the configuration of the web app when calling a web API. For instance, in the appsettings.json file. "
Am I not using a token to call the endpoint? Why do I need a client secret or certificate? Can I do without? The token that the web API get from the caller is from an authenticated user and the web API has delegated permission of the user?
I am new to this so please bear with me. Thanks!
Update2:
After adding the client secret, I am getting the following exception:
Update3:
public void ConfigureServices(IServiceCollection services)
{
var principalProvider = new ClaimsPrincipalProvider();
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Events = new JwtBearerEvents()
{
OnTokenValidated = async ctx =>
{
try
{
var tokenAcquisition = ctx.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }, user: ctx.Principal, authenticationScheme: JwtBearerDefaults.AuthenticationScheme);
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}));
var groups = await graphClient.Me.CheckMemberGroups(
new[]
{
Constants.GroupId
})
.Request()
.PostAsync();
}
catch (System.Exception ex)
{
}
}
};
});
services
.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddMicrosoftIdentityWebApi(this.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph()
.AddInMemoryTokenCaches();
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
}
I tried to reproduce the scenario in my environment using postman.
I excluded client secret/certificate while getting the token to the application for the user.
I received similar error :
Basicallly, when Web apps call web APIs we need some valid secret token to give access to secure app.In general they are confidential client applications. So the secret that is registered in the azure ad for the app must be passed during the call to Azure AD endpoint to get a token.
Then I provided a valid value of secret to the endpoint and received to token successfully to call my Api.
In your app it must be included in
appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "[xxxxx]",
"TenantId": "<tenantId here>"
// To call an API
"ClientSecret": "[Copy the client secret added to the app from the Azure portal]",
},
"MyApi": {
"BaseUrl": "https://graph.microsoft.com/beta",
"Scopes": "https://graph.microsoft.com/.default"
}
}
And then received the group members successfully.
Reference: Build a web app that authenticates users and calls web APIs - Microsoft Entra | Microsoft Learn

signalr core (2.1) JWT authentication hub/negotiate 401 Unauthorized

so I have a .net core (2.1) API that uses JWT tokens for authentication. I can login and make authenticated calls successfully.
I am using React (16.6.3) for the client, which getting a JWT code and making authenticated calls to the API works.
I am trying to add signalr hubs to the site. If I do not put an [Authorize] attribute on the hub class. I can connect, send and receive messages (its a basic chathub at the moment).
when I add the [Authorize] attribute to the class, the React app will make an HttpPost to example.com/hubs/chat/negotiate . I would get a 401 status code. the Authorization: Bearer abc..... header would be passed up.
To build the hub in React I use:
const hubConn = new signalR.HubConnectionBuilder()
.withUrl(`${baseUrl}/hubs/chat`, { accessTokenFactory: () => jwt })
.configureLogging(signalR.LogLevel.Information)
.build();
where the jwt variable is the token.
I have some setup for the authentication:
services.AddAuthentication(a =>
{
a.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
a.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = false;
options.Audience = jwtAudience;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey)),
};
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var authToken = context.Request.Headers["Authorization"].ToString();
var token = !string.IsNullOrEmpty(accessToken) ? accessToken.ToString() : !string.IsNullOrEmpty(authToken) ? authToken.Substring(7) : String.Empty;
var path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = token;
}
return Task.CompletedTask;
}
};
});
the OnMessageReceived event does get hit and context.Token does get set to the JWT Token.
I can't figure out what I am doing wrong to be able to make authenticated calls for signalr core.
solution
I updated my code to use 2.2 (not sure if this was actually required).
so I spent some time looking at the source code, and the examples within:
https://github.com/aspnet/AspNetCore
I had a Signalr CORS issue which was solved with:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed((host) => true) //allow all connections (including Signalr)
);
});
the important part being .SetIsOriginAllowed((host) => true) This allows all connections for both website and signalr cors access.
I had not added
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
I had only used services.AddAuthentication(a =>
I took the following directly from the samples in github
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}
};
Not sure if this was needed in the attribute, but the same used it on its hubs
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
with that I could not get multiple website and console apps to connect and communicate via signalr.
To be used with [Authorize] you need to set the request header. Since web sockets do not support headers, the token is passed with the query string, which you correctly parse. The one thing that's missing is
context.Request.Headers.Add("Authorization", "Bearer " + token);
Or in your case probably context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);
Example:
This is how i do it. On the client:
const signalR = new HubConnectionBuilder().withUrl(`${this.hubUrl}?token=${token}`).build();
On the server, in Startup.Configure:
app.Use(async (context, next) => await AuthQueryStringToHeader(context, next));
// ...
app.UseSignalR(r => r.MapHub<SignalRHub>("/hubUrl"));
Implementation of AuthQueryStringToHeader:
private async Task AuthQueryStringToHeader(HttpContext context, Func<Task> next)
{
var qs = context.Request.QueryString;
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"]) && qs.HasValue)
{
var token = (from pair in qs.Value.TrimStart('?').Split('&')
where pair.StartsWith("token=")
select pair.Substring(6)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
}
await next?.Invoke();
}
best method for using authorization for hubs is to force the application add the jwt token from the query string to the context
and its working for me via this method
put this inside your program.cs (dot net 6):
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
o.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (string.IsNullOrEmpty(accessToken) == false)
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
and my hubs are like this its using the main authorize method of asp
[Microsoft.AspNetCore.Authorization.Authorize]
public async Task myhub()
{
//do anything in your hub
}
youtube totorial that helped me to solve this problem

Passport-jwt issue : JWT token is working with postman but not working with UI api call

I have integrated passport-jwt for authentication purpose. It's working like charm but whenever Frontend guy use it from frontend angular 2 its giving Unauthorised 401 . I've tried alot but not getting any clue, it must be a silly mistake though.
my passport strategy file is as
let JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
//let fromHeader = require('passport-jwt').fromHeader
// load up the user model
const User = require('../components/user/model');
const database = require('./database'); // get db config file
const config = require('./config'); // get db config file
module.exports = function(passport) {
//var passportStrategy = function(passport){
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
//opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("JWT");
console.log("opts.jwtFromRequest==",opts.jwtFromRequest);
opts.secretOrKey = config.secret;//config.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
//console.log("opt==",JSON.stringify(opt));
//console.log("jwt_payload===",jwt_payload);
User.findOne({_id: jwt_payload._doc._id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};
my route is as
app.get("/api/user/getAll",
passport.authenticate('jwt',{session:false}),
userController.fetchUsers
);
And frontend header append is as follows :
logoutUser(token) {
//const userData = JSON.stringify(userInfo);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', token); //e.g.token = JWT dasddddasdsda
//headers.append('Authentication', token);
console.log(headers)
return this.http.post('http://localhost:9000/api/user/logout', { headers: headers })
.map((response: Response) =〉 {
return response.json()
})
.catch(this.errorHandler);
}
It would really great if anyone can assist me further to identify the mistake.
Second argument for the post method is payload.
so this code below
this.http.post('http://localhost:9000/api/user/logout', { headers: headers })
has to be
this.http.post('http://localhost:9000/api/user/logout', {}, { headers: headers })

API Gateway Custom Authorizer for Federated Identity

I created a custom authorizer for API Gateway so that i can pass the Facebook token and it will authenticate it using Cognito's Federated identity.
My problem is that the fb token seems to expire so I keep getting 403 errors. I am wondering if my approach is correct. Should I pass the Facebook token as part of the request header to API gateway on every REST API call or so I pass AWS identity id instead. Any feedback is appreciated. Thank you.
var AWS = require('aws-sdk');
var cognitoidentity = new AWS.CognitoIdentity();
exports.handler = (event, context, callback) => {
var params = {
IdentityPoolId: 'us-west-2:xxxxxxxxxxxxxxxxx’, /* required */
AccountId: ‘xxxxxxxxxxxxxxxxx,
Logins: {
'graph.facebook.com': event.authorizationToken //Token given by Facebook
}
};
console.log(event.methodArn);
cognitoidentity.getId(params, function(err, data) {
if (err) {
console.log(err);
callback(null, generatePolicy('user', 'Deny', event.methodArn));
}
else{
console.log("success");
callback(null, generatePolicy('user', 'Allow', event.methodArn));
}
});
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // default version
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke'; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
}

Angular2 Stripe integration stripeResponseHandler cannot access this

I'm integrating Stripe payments with Angular2 (actually Ionic but the code is the same)
the call to Stripe.card.createToken is successful and returns a token
but in stripeResponseHandler which is an async callback, I cannot access any of the "this" variables. for example I cannot set this.amount = 10 and I cannot call this._http.post
how can I access the "this" variables ? I'm trying to http post the token and the amount to an API to make the payment
constructor(private _navController: NavController,
private _http: Http) { }
submitPayment() {
Stripe.setPublishableKey(this.key);
this.card = new Card();
this.card.number = this.cardNumber;
this.card.cvc = this.cardCVC;
this.card.exp_month = this.cardExpMonth;
this.card.exp_year = this.cardExpYear;
this.card.address_zip = this.cardAddressZip;
try {
Stripe.card.createToken(this.card, this.stripeResponseHandler);
}
catch (e) {
alert(e.message);
}
// Prevent the form from being submitted:
return false;
}
stripeResponseHandler(status, response) {
if (response.error) { // Problem!
alert(response.error);
} else { // Token was created!
// Get the token ID:
alert(response.id);
try {
this.amount = 10;
let payment = new Payment();
payment.token = response.id;
payment.amount = this.amount;
let body = JSON.stringify(payment);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
this._http.post(this.url, body, options)
.map(res => res.json())
.catch(this.handleError);
}
catch (e) {
alert(e.message);
}
}
}
handleError(error: Response) {
// may send the error to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
alert('error' + error.text + " " + error.statusText);
return Observable.throw(error.json().error || 'Server error');
}
If you just pass the function reference, then JavaScript doesn't keep the this reference. You have to take care of this explicitely:
Instead of
Stripe.card.createToken(this.card, this.stripeResponseHandler);
use
Stripe.card.createToken(this.card, (status, person) => this.stripeResponseHandler(status, person));
See also https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions
or
Stripe.card.createToken(this.card, this.stripeResponseHandler.bind(this));