Vue app and .NET Core Web Api losing session - vue.js

I hope this has not already been asked, I can't seem to find what I need. I have a VUE 3 app and am using a .NET Core Web API to retrieve data from a service. In the Vue app I make an axios call to log in the user
await axios({
method: 'post',
url: 'https://localhost:44345/api/Authentication/SignIn',
contentType: "application/json",
params: {
username: signInData.value.username,
password: signInData.value.password,
keepMeSignedIn: signInData.value.keepMeSignedIn
}
}).then(response => {
if (response.data.succeeded) {
console.log("Result: ", response.data.data);
}
else {
emit('handleServerSideValidationErrors', response);
}
This then calls my API where I call the service to sign in the user. Once I have verified the information and have the user data it is getting set in session.
public void Set<T>(string key, T value)
{
if (key.IsNullOrEmpty())
{
throw new Exception("The key parameter for SessionUtil.Set is required. It cannot be null/empty.");
}
else
{
this._validateSessionObjectVersion();
if (value == null)
{
Remove(key);
}
else
{
string json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
_httpContextAccessor.HttpContext.Session.SetString(key, json);
}
}
}
The issue I am running into is, when I go to another page that needs to access this session it is null. The API calls this get method but is null.
public T Get<T>(string key)
{
T value = default(T);
if (key.IsNullOrEmpty())
{
return value;
}
if (_httpContextAccessor.HttpContext == null)
{
return value;
}
this._validateSessionObjectVersion();
string json = _httpContextAccessor.HttpContext.Session.GetString(key);
if (!json.IsNullOrEmpty())
{
value = JsonConvert.DeserializeObject<T>(json);
}
return value;
}
My Vue app is running on localhost:5001 while my API is running on localhost:44345. I do have a cors policy already in place which allows me to call the API but I don't see what I need to do in order to not lose session.

Turns out my issue was I had set the cookie option of SameSite to SameSiteMode.Lax. As soon as I changed it to SameSiteMode.None it was working for me.

Related

SignalR Azure Service with stand alone Identity Server 4 returns 401 on negotiaton

We have a ASP.Net Core application that authenticates against a standalone Identity Server 4. The ASP.Net Core app implements a few SignalR Hubs and is working fine when we use the self hosted SignalR Service. When we try to use the Azure SignalR Service, it always returns 401 in the negotiation requests. The response header also states that
"Bearer error="invalid_token", error_description="The signature key
was not found"
I thought the JWT-Configuration is correct because it works in the self hosted mode but it looks like, our ASP.Net Core application needs information about the signature key (certificate) that our identity server uses to sign the tokens. So I tried to use the same method like our identity server, to create the certificate and resolve it. Without luck :-(
This is what our JWT-Configuration looks like right now:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => {
var appSettings = Configuration.Get<AppSettingsModel>();
options.Authority = appSettings.Authority;
options.RefreshOnIssuerKeyNotFound = true;
if (environment.IsDevelopment()) {
options.RequireHttpsMetadata = false;
}
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
ValidateAudience = false,
IssuerSigningKey = new X509SecurityKey(getSigningCredential()),
IssuerSigningKeyResolver = (string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) =>
new List<X509SecurityKey> { new X509SecurityKey(getSigningCredential()) }
};
options.Events = new JwtBearerEvents {
OnMessageReceived = context => {
var accessToken = "";
var headerToken = context.Request.Headers[HeaderNames.Authorization].ToString().Replace("Bearer ", "");
if (!string.IsNullOrEmpty(headerToken) && headerToken.Length > 0) {
accessToken = headerToken;
}
var queryStringToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(queryStringToken) && queryStringToken.ToString().Length > 0) {
accessToken = queryStringToken;
}
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) {
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Update:
We also have a extended the signalR.DefaultHttpClient in our Angular Client and after playing around a bit, I noticed the application is working fine without it:
export class CustomSignalRHttpClientService extends signalR.DefaultHttpClient {
userSubscription: any;
token: string = "";
constructor(private authService: AuthorizeService) {
super(console); // the base class wants a signalR.ILogger
this.userSubscription = this.authService.accessToken$.subscribe(token => {
this.token = token
});
}
public async send(
request: signalR.HttpRequest
): Promise<signalR.HttpResponse> {
let authHeaders = {
Authorization: `Bearer ${this.token}`
};
request.headers = { ...request.headers, ...authHeaders };
try {
const response = await super.send(request);
return response;
} catch (er) {
if (er instanceof signalR.HttpError) {
const error = er as signalR.HttpError;
if (error.statusCode == 401) {
console.log('customSignalRHttpClient -> 401 -> TokenRefresh')
//token expired - trying a refresh via refresh token
this.token = await this.authService.getAccessToken().toPromise();
authHeaders = {
Authorization: `Bearer ${this.token}`
};
request.headers = { ...request.headers, ...authHeaders };
}
} else {
throw er;
}
}
//re try the request
return super.send(request);
}
}
The problem is, when the token expires while the application is not open (computer is in sleep mode e.g.), the negotiaton process is failing again.
I finally found and solved the problem. The difference of the authentication between "self hosted" and "Azure SignalR Service" is in the negotiation process.
Self Hosted:
SignalR-Javascript client authenticates against our own webserver with
the same token that our Javascript (Angular) app uses. It sends the
token with the negotiation request and all coming requests of the
signalR Http-Client.
Azure SignalR Service:
SignalR-Javascript client sends a negotiation request to our own
webserver and receives a new token for all coming requests against the
Azure SignalR Service.
So our problem was in the CustomSignalRHttpClientService. We changed the Authentication header to our own API-Token for all requests, including the requests against the Azure SignalR Service -> Bad Idea.
So we learned that the Azure SignalR Service is using it's own token. That also means the token can invalidate independently with our own token. So we have to handle 401 Statuscodes in a different way.
This is our new CustomSignalRHttpClientService:
export class CustomSignalRHttpClientService extends signalR.DefaultHttpClient {
userSubscription: any;
token: string = "";
constructor(private authService: AuthorizeService, #Inject(ENV) private env: IEnvironment, private router: Router,) {
super(console); // the base class wants a signalR.ILogger
this.userSubscription = this.authService.accessToken$.subscribe(token => {
this.token = token
});
}
public async send(
request: signalR.HttpRequest
): Promise<signalR.HttpResponse> {
if (!request.url.startsWith(this.env.apiUrl)) {
return super.send(request);
}
try {
const response = await super.send(request);
return response;
} catch (er) {
if (er instanceof signalR.HttpError) {
const error = er as signalR.HttpError;
if (error.statusCode == 401 && !this.router.url.toLowerCase().includes('onboarding')) {
this.router.navigate([ApplicationPaths.Login], {
queryParams: {
[QueryParameterNames.ReturnUrl]: this.router.url
}
});
}
} else {
throw er;
}
}
//re try the request
return super.send(request);
}
}
Our login-Route handles the token refresh (if required). But it could also happen, that our own api-token is still valid, but the Azure SignalR Service token is not. Therefore we handle some reconnection logic inside the service that creates the SignalR Connections like this:
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe(async (page: NavigationEnd) => {
if (page.url.toLocaleLowerCase().includes(ApplicationPaths.Login)) {
await this.restartAllConnections();
}
});
hope this helps somebody

Stripe ASP.NET Core Webhook always returns BadRequest

I am currently working on a project and integrating Stripe payment. So far I have done the Stripe Checkout Session with the prebuilt template from here - https://stripe.com/docs/payments/accept-a-payment?integration=checkout. This is what I have done:
[HttpPost]
public async Task<ActionResult> CreateCheckoutSession()
{
var consultation = await this.offerService.GetOnlineConsultationModelAsync();
var options = new SessionCreateOptions
{
CustomerEmail = this.User.FindFirst(ClaimTypes.Email).Value,
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
PriceData = new SessionLineItemPriceDataOptions
{
UnitAmount = (long)(consultation.Price * 100),
Currency = "bgn",
ProductData = new SessionLineItemPriceDataProductDataOptions
{
Name = consultation.Name,
Images = new List<string>
{
"https://res.cloudinary.com/dpo3vbxnl/image/upload/v1641585449/pediamed/onlineConsultation_vyvebl.jpg"
}
},
},
Quantity = 1,
},
},
PaymentIntentData = new SessionPaymentIntentDataOptions
{
CaptureMethod = "automatic",
},
PaymentMethodTypes = new List<string>
{
"card"
},
Mode = "payment",
SuccessUrl = "http://pediamed-001-site1.btempurl.com/Checkout/SuccessfulPayment",
CancelUrl = "http://pediamed-001-site1.btempurl.com/Checkout/CanceledPayment",
};
var service = new SessionService();
try
{
var session = service.Create(options);
await this.paymentService.CreateChekoutSession(session.Id, session.PaymentIntentId, session.ClientReferenceId);
Response.Headers.Add("Location", session.Url);
return new StatusCodeResult(303);
}
catch (StripeException ex)
{
System.Console.WriteLine(ex.StripeError.Message);
return BadRequest(new { Message = ex.StripeError.Message });
}
}
So far so good - everything works. Then I created the template from the Fulfill your orders article - https://stripe.com/docs/payments/checkout/fulfill-orders. This is what I currently have:
[HttpPost]
public async Task<IActionResult> Index()
{
string secret = "whsec_......";
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ConstructEvent(
json,
Request.Headers["Stripe-Signature"],
secret
);
// Handle the checkout.session.completed event
if (stripeEvent.Type == Events.CheckoutSessionCompleted)
{
var session = stripeEvent.Data.Object as Session;
// Fulfill the purchase...
await this.FulfillOrder(session);
}
return Ok();
}
catch (StripeException ex)
{
return BadRequest(new {message = ex.StripeError.Message});
}
}
The problem is when I use the Stripe CLI I get this:
Every time I get Bad Request, even in the details of the events in the Stripe Dashboard, but if I go to the payments dashboard they are all succeeded. Everything is done in localhost, but when deployed and using the correct api keys it is all the same. Can anyone help me with the Stripe webhook.
I suspect you might be using the signing secret from your dashboard, but when forwarding via the CLI using listen you need to use the CLI-specific signing secret that's provided in output when you run listen.
If that's not the case, can you provide more detail about the exception that's raised that leads to the bad request response path? ie, where in your code do you encounter an error?

How to configure axios to request WebAPI with auth?

I have a local ASP.Net FrameWork WebAPI server with the following controller:
public class ValuesController : ApiController
{
// GET api/values
[AuthorizationFilter]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
I created an AuthorizationFilter attribute to handle authorization (only for GET with no id action):
public class AuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext ctx)
{
if(ctx.Request.Headers.Authorization == null)
{
ctx.Response = ctx.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
} else
{
string authenticationString = ctx.Request.Headers.Authorization.Parameter;
string decodedAuthString = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationString));
string username = decodedAuthString.Split(':')[0];
string password = decodedAuthString.Split(':')[1];
// assume that I have checked credentials from DB
if (username=="admin" && password=="root")
{
// authorized...
} else
{
ctx.Response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Also, I modified Web.config to allow CORS:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
...
</system.webServer>
I ran the server and tried to get /api/values/1 from the browser, and it worked.
I then tried to access the action that requires authorization: /api/values :
I used Insomnia to send requests and test CORS. First I tried the action that doesn't require authorization:
Then I tried the action with authorization:
Then I tried the same action but after adding the authentication username and password, and that worked out fine:
After this point, I knew my webapi is configured correctly, so I tried to pull data from a React app using axios:
const api = axios.create({
baseURL: "http://localhost:50511/api",
});
const response = await api.get("/values/1");
console.log(response.data); // works fine: I get "value" as expected
And now, the final step, to configure axios to request the action that requires authentication:
const api2 = axios.create({
baseURL: "http://localhost:50511/api",
auth : {
username: "admin",
password: "root"
}
});
const response = await api2.get("/values"); // causes a network exception
The reported error is strange, since it talks about CORS. I don't get it. If there shall be an error, it can imagine it being an error related to authorization. Not CORS. Not after being able to use axios to pull data from the action that has no authentication filter.
I examined the request header to make sure that it was configured with the correct Authorization parameter:
I also tried to use axios in different ways:
const response1 = await axios.get("http://localhost:50511/api/values",{
auth: {
username: "admin",
password: "root"
}
});
const response2 = await axios.get("http://localhost:50511/api/values",{
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Both of these attempts did not work.
Then I tried again, but this time passing an empty object as the second parameter to the axios call:
const response3 = await axios.get("http://localhost:50511/api/values", {}, {
auth: {
username: "admin",
password: "root"
}
});
const response4 = await axios.get("http://localhost:50511/api/values", {}, {
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Again, none of these attempts worked. What am I don't wrong?

Issue Application Id for each kind of Client Application

I'm using Microsoft Owin and ASP.NET WebApi for authentication and authorization process for my client application. Also the authentication server is secured by HTTPS. I've read a few articles about using Microsoft Owin, one of them which I've chosen to implement is:
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
There are some differences between my project and that implementation:
I need to identify my client in case the request is sent by my application on the mobile phone, not any other devices or tools like Fiddler. I think one the options could be sending an application id by each request from the mobile application. But I don't know how and where should I verify the requests in authentication server application. This is really important for registering users:
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(UserModel userModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await _repo.RegisterUser(userModel);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
I don't want to let unreliable devices, i.e. the clients except the mobile application, to call this method.
I need to let anonymous users to buy some products from the website, but I don't know what is the best practice to issue token for anonymous users without doing authentication.
If you want to identify your client and authorize it you can override the method ValidateClientAuthentication.
In Taiseer's example you have linked you will find some code:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
and a note which says:
As you notice this class inherits from class
“OAuthAuthorizationServerProvider”, we’ve overridden two methods
“ValidateClientAuthentication” and “GrantResourceOwnerCredentials”.
The first method is responsible for validating the “Client”, in our
case we have only one client so we’ll always return that its validated
successfully.
If you want to validate the client you have to put some logic in there.
Normally you would pass a clientId and a clientSecret in the header of your http request, so that you can validate the client's request with some database parameters, for example.
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
return;
}
try
{
// You're going to check the client's credentials on a database.
if (clientId == "MyApp" && clientSecret == "MySecret")
{
context.Validated(clientId);
}
else
{
// Client could not be validated.
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
catch (Exception ex)
{
string errorMessage = ex.Message;
context.SetError("server_error");
context.Rejected();
}
return;
}
In the sample above you will try to extract the client credentials sent in the header of your request:
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
and validated them:
// You're going to check the client's credentials on a database.
if (clientId == "MyApp" && clientSecret == "MySecret")
{
context.Validated(clientId);
}
if the client is sending a wrong request header you need to reject the request:
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
The method ValidateClientAuthentication is processed before GrantResourceOwnerCredentials. This way you can extend it and pass GrantResourceOwnerCredentials some extra information you might need there.
In one of my applications I've created a class:
class ApplicationClient
{
public string Id { get; set; }
public string Name { get; set; }
public string ClientSecretHash { get; set; }
public OAuthGrant AllowedGrant { get; set; }
public DateTimeOffset CreatedOn { get; set; }
}
which I use in ValidateClientAuthentication right after I've checked the clientId and the secret are ok:
if (clientId == "MyApp" && clientSecret == "MySecret")
{
ApplicationClient client = new ApplicationClient();
client.Id = clientId;
client.AllowedGrant = OAuthGrant.ResourceOwner;
client.ClientSecretHash = new PasswordHasher().HashPassword("MySecret");
client.Name = "My App";
client.CreatedOn = DateTimeOffset.UtcNow;
context.OwinContext.Set<ApplicationClient>("oauth:client", client);
context.Validated(clientId);
}
As you can see here
context.OwinContext.Set<ApplicationClient>("oauth:client", client);
I am setting a Owin variable which I can read later on. In your GrantResourceOwnerCredentials now you can read that variable in case you need it:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
ApplicationClient client = context.OwinContext.Get<ApplicationClient>("oauth:client");
...
}
Now, if you want to fetch the bearer token - which you're going to use for all the secure API calls - you need to encode your clientId and clientSecret (base64) and pass it in the header of the request:
An ajax request with jquery would look something like this:
var clientId = "MyApp";
var clientSecret = "MySecret";
var authorizationBasic = $.base64.btoa(clientId + ':' + clientSecret);
$.ajax({
type: 'POST',
url: '<your API token validator>',
data: { username: 'John', password: 'Smith', grant_type: 'password' },
dataType: "json",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
xhrFields: {
withCredentials: true
},
headers: {
'Authorization': 'Basic ' + authorizationBasic
},
beforeSend: function (xhr) {
},
success: function (result) {
var token = result.access_token;
},
error: function (req, status, error) {
alert(error);
}
});
As you can see I've also added the username and password - with the grant type - in the body of the request:
data: { username: 'John', password: 'Smith', grant_type: 'password' }
so that the server will be able to validate the client (clientId + clientSecret) and the user (username + password).
If the request is successful you should get back a valid token:
oAuth.Token = result.access_token;
which you can store somewhere for the following requests.
Now you can use this token for all the requests to the api:
$.ajax({
type: 'GET',
url: 'myapi/fetchCustomer/001',
data: { },
dataType: "json",
headers: {
'Authorization': 'Bearer ' + oAuth.Token
},
success: function (result) {
// your customer is in the result.
},
error: function (req, status, error) {
alert(error);
}
});
Another thing you might want to add to your API during the startup is SuppressDefaultHostAuthentication:
config.SuppressDefaultHostAuthentication();
this is an extension method of HttpConfiguration. Since you're using bearer tokens you want to suppress the standard cookie-based authentication mechanism.
Taiseer has written another series of articles which are worth reading where he explains all these things.
I have created a github repo where you can see how it works.
The Web API is self-hosted and there are two clients: jQuery and Console Application.

Sails JS with Redis for caching

As I said in my previous questions, I am trying to learn how to use sails.js, what I'm trying to do now is to cache the response of an api to redis. I have searched on how to do this, but I can't make it to work. Without caching, I call the api through ajax.
Any thoughts on how I will be able to do it using my controller? How can I call the api using the controller in sails.js and cache the response using redis?
You can use https://github.com/mranney/node_redis
Steps:
Add to package.json
"redis": "^0.12.1"
Run
npm install
Create a service module /api/services/CachedLookup.js
var redis = require("redis"),
client = redis.createClient();
module.exports = {
rcGet: function (key, cb) {
client.get(key, function (err, value) {
return cb(value);
});
},
fetchApi1: function (cb) {
var key = 'KEY'
CachedLookup.rcGet(key, function (cachedValue) {
if (cachedValue)
return cb(cachedValue)
else {//fetch the api and cache the result
var request = require('request');
request.post({
url: URL,
form: {}
}, function (error, response, body) {
if(error) {
//handle error
}
else {
client.set(key, response);
return cb(response)
}
});
}
});
}
}
Inside the controller
CachedLookup.fetchApi1(function (apiResponse) {
res.view({
apiResponse: apiResponse
});
});