Cant set cookie when CORS request comes from iPhone. Works for windows and mac users - cross-domain

The Setup / Environment
Client: (React.js, vs code, axios)
POST request to backend server to set auth cookie.
On every refresh Ill verify the cookie by a GET request to the auth backend server.
Every axios call is done with the "withCredentials:true" property set.
Backend (.net 6 - miminal API app written in c# and visual studio 2022.)
Set the cookie to "httpOnly", "sameSite=none" and "secure".
What Works
I have tested the flow on Windows 10, 11 + Mac computer and here everythink works fine. The cookie is set and I can login to my app.
The setCookie header is present here.
The problem
When I try to login from my iPhone with the latest IOS 15.4 it is not working (though it should be supported according to this https://caniuse.com/same-site-cookie-attribute).
The cookie is not set and the "getcookie" request returns null and Ill return Unauthorized.
Code:
Frontend (React js):
//run "npx create-react-app iphone-cors-test-app" and add a useEffect hook in App component //like this.
useEffect(() => {
var urlToBackendService= "https://829f-217-211-155-130.eu.ngrok.io";
axios({
url: baseURL + '/setcookie',
method: 'post',
withCredentials: true,
data: {
Email: 'ineedhelp#please.com',
Password: 'loveu'
}
}).then(() => {
axios({
url: baseURL + '/getcookie',
method: 'get',
withCredentials: true
}).then((resp) => {
var cookieValue = resp.data;
console.clear();
console.log(`cookie value: ${cookieValue}`);
alert(`cookie value: ${cookieValue}`);
})
});
Backend (c# .net 6, visual studio 2022):
//.net core web api template > minimal api (no controllers) enable https.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://nameofmyreactapp.azurewebsites.net")
.WithHeaders("accept", "content-type", "origin")
.WithMethods("GET", "POST", "OPTIONS")
.AllowCredentials();
});
});
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors();
app.MapPost("/setcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor, LogonRequest logonRequest) =>
{
return await Task.Run<IResult>(() =>
{
//login user and get an accesstoken. set accesstoken to a httpOnly cookie.
var accessToken = "newly generated jwt access token";
httpContextAccessor.HttpContext!.Response.Cookies.Append(key: "accesstoken", accessToken, new CookieOptions
{
HttpOnly = true,
/*This should work with an iPhone with ios 15.4 (https://caniuse.com/same-site-cookie-attribute).*/
SameSite = SameSiteMode.None,
Secure = true
});
return Results.Ok();
});
});
app.MapGet("/getcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor) =>
{
return await Task.Run<IResult>(() =>
{
var accesstoken = httpContextAccessor.HttpContext!.Request.Cookies["accesstoken"];
return string.IsNullOrEmpty(accesstoken)
? Results.Unauthorized()
: Results.Ok(accesstoken);
}
);
});
app.Run();
public record LogonRequest(string Username, string Password);
Screenshots:
setCookie request.
getCookie request.
Please help me.
UPDATE
If you want to test this with your phone I use ngrok. Sign up and follow directions. Download ngrok.exe and go to that folder in your terminal. Then start your backend localhost and type "ngrok http + your localhost address".
Example:
"ngrok http https://localhost:7200"
Then hit enter and you will get a public url to your localhost.
Replace the client axios url (urlToBackendService) with this new public url and publish your react app to to cloud (or create another ngrok user and do the same think for the client) or if you have browserstack account or if you have another idé how to test this.

I just want to clarify the solution here.
(Solution 2 is the best practice version here if you just want the short version)
Solution 1
I knew that it probably should work if my two sites where hosted on the same domain but since I was in early stages in development and I didnt want to create custom domains just yet, and I also had read documentation that interpreted that is should work anyways I continued with this solution.
So my first solution (which is not idéal since localstorage is not as safe as a secure httponly cookie) was to change my auth server to be able to receive accesstoken via headers and cookies, I also made sure to return tokens in the response so I could store the tokens in localstorage. Flow example:
login with username & password and send form to auth server.
Get tokens from auth server response and store in a local storage variable.
Send a request to auth server with accesstoken header provided by localstorage variable.
Solution 2 (Best practice version)
Cred to my fellow user #AndrewLeonardi and the original post from #RossyBergg which could confirmed what I expected, that it will work if I just put the two services on the same domain. I ended up with this solution:
AuthService url: https://auth.domain.se
Client url: https://domain.se
The httpOnly secure cookies was now working properly and I was able to get, set & remove the cookie in the auth server. The header & localstorage implementation from "Solution 1" could be removed.

Related

How do I access Express.js Cookie in React app?

I'm creating E-shop with MERN Stack
This is my response from the server on Login where you can see, the cookie is SET and it's sent from the Backend.
That means no there shoudln't be problem with BE, but the FE I will need to handle the Cookie on the FE.
How do I access this sent Cookie from Express in the React ?
I have tried something like this:
const handleLogin = async (event) => {
event.preventDefault();
try {
const url = "http://localhost:5000/api/auth/login";
const data = await axios.post(url, formFields);
const { user } = data.data;
// Here I have tried to access it from Headers where I can see it in the attached
// picture under Set-Cooki. But inside headers I can only see
// Content-Length and Content-Type
console.log(data.headers);
// Aswell I have tried react-cookie but docs are not clear enough for me.
// and this just sets cookie from react, I would like to use cookie from express
// and set it like this
// await setCookie("user", "INSERT_HERE_COOKIE_FROM_EXPRESS.JS", {
// path: "/",
// });
setCurrentUser(user);
await resetFormFields();
} catch (error) {
const data = error.response.data;
!data.error
? alert(`${data.message}`)
: alert(`${data.message}: ${data.error}`);
}
};
Thank you for any answers, I'm sure it's not that hard as I think and it's few lines of code.
As I see on your screenshot - you use express with httpOnly cookies:
https://developer.mozilla.org/ru/docs/Web/HTTP/Cookies -
A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it's only sent to the server. For example, cookies that persist in server-side sessions don't need to be available to JavaScript and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.
And I think you don't want to use nonsecure cookies in your E-shop - so you can't access it, but you can use custom headers, so on your frontend it will be like:
fetch('/myapi').then(response => console.log(response.headers.get('myCustomHeader')));

decode firebase oobCode to user on server side

Having some issues regarding a custom email flow and how to decode users on the backend.
see flow:
user: (webapp)
enter email address and send request for a password reset
server:
generate oobCode using nodeJS SDK: admin.auth().generatePasswordResetLink(email);
send link in custom email to user
user: (webapp)
clicks link -> www.example.com?oobCode=XYZ
client SDK verify's token has not expired: firebase.auth().verifyPasswordResetCode("XYZ");
if valid send oobCode and new password back to server
Here is where the issue now occurs
I have sent the new password and the oobCode back to the server, below is the required functionality
server:
1. verify oobCode and get user uid
I see no methods to be able to decode this in the backend to verify the user how do I go about getting a user from this code on the backend? it seems we can generate but not decode
Try running this code to verify oobCode for password reset:
const passResetUrl = `https://identitytoolkit.googleapis.com/v1/accounts:resetPassword?key=${firebaseWebApiKey}`
return fetch(passResetUrl, { method: 'POST', body: JSON.stringify({ "oobCode": oobCode, newPassword: "newpass" }), headers: { "Content-Type": "application/json" } }).then(async (res) => {
const resJson = await res.json()
//const email = resJson.email
console.log(resJson)
return "ok"
}).catch((errVerifyingCode) => {
console.log(errVerifyingCode)
return "error"
})
The firebaseWebApiKey can be found in your web app's Firebase configuration.
If by decode you meant get user auth object then I don't think that's possible. Consider adding some sort of query param in your generated url that'll help your server identify the user.
If you verify the oob code on server instead of using Firebase Client SDK as shown above you'll get the user's email back in response.

Node.js Axios HTTP request with bearer token returning undefined

I've seen many posts on Stack that are close to what I need, but don't fully answer my question (I'm pretty green with Node). I'm working on connecting a Twitch/Tiltify donation campaign to a Raspberry Pi via Node.js/Axios. I'd like the Pi to regularly check for new donations, then activate physical circuits (solenoid valves etc.) to be viewed live on the stream. Here's my code so far:
const axios = require('axios');
axios.get('URL_GOES_HERE', {
headers: {
'Authorization' : 'Bearer MY_TILTIFY_ACCESS_TOKEN'
}
})
.then(response => {
console.log(response.data.url);
console.log(response.data.explanation);
})
.catch(error => {
console.log(error);
});
I assume that MY_TILTIFY_ACCESS_TOKEN is the access token I generated from within my Tiltify account. I'm confused, however, about what value to put in URL_GOES_HERE. The somewhat sparse Tiltify API docs give two possible URLS: https://tiltify.com/oauth/authorize and https://tiltify.com/oauth/token. Or am I supposed to put my bearer credentials directly into the URL of a useful request, like https://tiltify.com/api/v3/user? I've tried all three, and I just get undefined undefined in the console.
A nudge in the right direction is appreciated! Thanks for your time.
#benstepp over on Github ultimately answered my question. Here's the code he provided:
const axios = require('axios');
axios.get('https://tiltify.com/api/v3/campaigns/MY_CAMPAIGN_ID/rewards', {
headers: {
'Authorization' : 'Bearer MY_API_TOKEN'
}
})
.then(response => { // this is an axios response object (https://github.com/axios/axios#response-schema)
//console.log(response.data); // this is the response body from tiltify (https://tiltify.github.io/api/endpoints/campaigns-id-donations.html)
//console.log(response.data.data); // this is the .data property of our responses
response.data.data.map((reward) => {
// the name/amount of the recent donations
console.log(`${reward.name}`)
})
})
.catch(error => {
console.log(error);
});
The /authorize endpoint is used for the Web Server OAuth Authentication Flow and User-Agent OAuth Authentication Flow.
The /token endpoint is used for the Username-Password OAuth Authentication Flow and the OAuth Refresh Token Process.
So first you need to get Authorized to be able to use Tiltify api. For that you need to use either of the flow
https://tiltify.com/oauth/authorize
https://tiltify.com/oauth/token
Assuming you used token route, you will get a response something like this:
{ "access_token":"token", "token_type":"bearer", "refresh_token":"refresh_token" }
Then using the access_token you got from the response you will call the api routes so in URL GOES HERE will be your api routes like
/campaigns/:id
causes/:id
with which you'll use Authorization: Bearer <access_token> in headers

Using cookies with axios and Vue

I have created a Node.js express server that connects to Salesforce.com using the SOAP interface provided by 'jsforce'. It uses session cookies for authorization via the 'express-session' package. So far, it has a POST method for login and a GET to perform a simple query. Testing with Postman has proven that this server is working as expected.
As the browser interface to this server, I have wrttien a Vue application that uses axios to perform the GET and POST. I need to save the session cookie created during login POST then attach attach the cookie to subsequent CRUD operations.
I have tried various methods to handle the cookies. One method I have tried is using axios response interceptors on the POST
axios.interceptors.response.use(response => {
update.update_from_cookies();
return response;
});
The function 'update_from_cookies' attempts to get the cookie named 'js-force' but it does not find it although I know it is being sent
import Cookie from 'js-cookie';
import store from './store';
export function update_from_cookies() {
let logged_in = Cookie.get('js-force');
console.log('cookie ' + logged_in);
if (logged_in && JSON.parse(logged_in)) {
store.commit('logged_in', true);
} else {
store.commit('logged_in', false);
}
}
I have also seen various recommendations to add parameters to the axios calls but these also do not work.
I would appreciate some advice about how to handle cookies using axios or some similar package that works with Vue
Thanks
The problem has been resolved. I was using the wrong syntax for the axios call
The correct syntax has the {withCredentials: true} as the last parameter
this.axios.post(uri, this.sfdata, {withCredentials: true})
.then( () => {
this.$router.push( {name : 'home' });
})
.catch( () => {
});

CORS on Web API and MVC 5 Controller: Upload images with fetch and FormData

I have an application that has the front and back ends running on different .NET projects.
The front end is an Aurelia web application running on ASP.NET 5. This Aurelia app (from now on The FrontEnd) gets all it's data from a Web API 2/MVC 5 application (henceforth, The BackEnd).
Since The FrontEnd and the BackEnd are different applications I have CORS setup, both for the Web API and in the Start.Auth.cs for the token bearer request.
The FronEnd is running on http://localhost:49850.
Now, for some code (this is all in the BackEnd)
Start.Auth.cs
The whole of the application resides behind a log-in form, so inside the Start.Auth.cs file, other than setting up the token-based authentication on the static Startup(), method I have a bit of middleware that adds the Access-Control-Allow-Origin header to the request on the one case where there is no token available yet: when we are requesting one.
public void ConfigureAuth(IAppBuilder app)
{
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.Equals("/token"))
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "http://localhost:49850" });
await next();
});
app.UseCors(CorsOptions.AllowAll);
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
WebApiConfig.cs
Here I just added the EnableCorsAttribute so that it is enable globally.
var enableCors = new EnableCorsAttribute("http://localhost:49850", "*", "*");
config.EnableCors(enableCors);
Uploading files
Everything works fine; I can perform GET and POST requests to the Web API without a problem, the problem comes when trying to upload images.
To upload to files I have an action method in an ASP.NET MVC controller called FileControler.
FileController.cs
[HttpPost]
public ActionResult UploadImage(string id, string name = "")
{
var files = (from string fileName in Request.File
select Request.Files[fileName]
into file
where file != null
select DoSomethingWithTheFile(file, id)).ToList();
// ...
return Json(arrayWithFileUrls);
}
Calling the MVC controller
This is already part of The FrontEnd.
To call this method I use Aurelia's Fetch Client:
upload(url, data, files) {
let formData = new FormData();
for (let key of Object.keys(data)) {
formData.append(key, data[key]);
}
for (let i = 0; i < files.length; i++) {
formData.append(`files[${i}]`, files[i]);
}
return this.http.fetch(url, {
method: "POST",
body: formData,
headers: {
cmsDatabase: sessionStorage["cmsDatabase"]
}
}).then(response => {
return response.json();
}).catch(error => {
console.log(error);
});
}
And here's a call to the upload method above:
// files comes from an <input type="file" />
upload("http://localhost:64441/file/uploadImage", { id: id }, files)
.then((uploadedPhotos) => {
// do something with those file urls...
});
The Problem
All this works if I remove all CORS setup from WebApiConfig.cs, and in Startup.Auth.cs I substitute the call to the middleware for app.UseCors(CorsOptions.AllowAll);, so I know my code is ok, but as soon as I use the CORS setup described above, everything works except the call to http://localhost:64441/file/uploadImage, returning even a 404:
Fetch API cannot load http://localhost:64441/file/uploadForSku.
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'
header is present on the requested resource.
Origin 'http://localhost:49850' is therefore not allowed access.
The response had HTTP status code 404. If an opaque response serves your needs,
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The "funny" thing is that if I try calling that url with, for intance, REST Console I don't get a 404.
I've tried adding the [HttpOptions] attribute to the action method; I've tried creating ActionFilterAttributes as described here and here, and even setting uip CORS from within the web.config, but to no avail.
I know the problem is that FileController is a regular MVC Controller instead of a Web API controlle, but shouldn't it still be possible to get CORS working?
have you tried this
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
in ApplicationOAuthProvider.cs file

Resources