How can i add the nonce in a frontend form submission - node.js

I am using NodeJS in my RestAPi app and I send the nonce on each HTTP request:
this.app.use(helmet());
this.app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(128).toString('hex');
next();
});
this.app.use(
helmet.contentSecurityPolicy({
useDefaults: true,
directives: {
scriptSrc: [
"'self'",
(_req, res) => { const x: string = `'nonce-${(res as Response).locals.cspNonce}'`; console.log(x); return x; },
],
},
})
);
The angular component that creates the form is like this:
constructor(fb: FormBuilder) {
this.form = fb.group({
name: [null],
email: [null],
notes: [null],
nonce: [null], <--- I need to place the nonce here
});
}
In the frontend i am using Angular/TS. Each http request made in the frontend app receives the nonce in the http header. This frontend does not uses cookies neither user authentication. But it has a form. I want to add the nonce from the http header to the form submission. This is just to prevent someone to replay the form submission or prevent someone to make a direct post to the API endpoint.
But i do not find any documentation how to make this work with the frontend and checking the incoming form on the backend.
Can anyone point me to a sample that shows how to do this? Thank you.

Related

"Sign in with Apple" redirects to a blank white page on PWA

I've implemented "Sign in with Apple" on my site. When I try it on my phone, it redirects me to a blank white page with the same URL as the redirect_uri I've configured.
I can't find any info on why this is happening. What's a possible fix?
UPDATE
It seems as if Apple JS SDK is creating a FORM HTML DOM element, sets the POST URL of the FORM to point to the redirect_uri, and finally programmatically clicks form.submit(). This for some reason causes the page to navigate to the redirect_uri and show the POST response as a new page.
I figured this out by tracking the Apple JS SDK in the debugger.
Here is my code
//---- Frontend ----
AppleID.auth.init({
clientId : '<client_id>',
scope : 'email',
redirectURI : 'mySite.com/apple_auth',
state : 'origin:web',
nonce : Date.now(),
//usePopup : true //not using this one. When false or undefined, Apple will make a POST request to the defined redirect_uri
})
// Listen for authorization success.
document.addEventListener('AppleIDSignInOnSuccess', (event) => {
// Handle successful response.
console.log(event.detail.data);
});
// Listen for authorization failures.
document.addEventListener('AppleIDSignInOnFailure', (event) => {
// Handle error.
console.log(event.detail.error);
});
//...
myButton.onClick = ()=>{
try {
var res = await AppleID.auth.signIn()
} catch(err) {
var x = 0
}
}
//---- Backend ----
var appleSignin = require("apple-signin-auth")
app.express.post('/apple_auth', async (req, res)=>{
var body = req.body
try {
const appleRes = await appleSignin.verifyIdToken(
body.id_token, // We need to pass the token that we wish to decode.
{
audience: '<client_id', // client id - The same one we used on the frontend, this is the secret key used for encoding and decoding the token.
ignoreExpiration: true, // Token will not expire unless you manually do so.
}
)
//do something with the Apple response
} catch (err) {
// Token is not verified
console.error(err)
}
})
From the documentation...
The HTTP body contains the result parameters with a content-type of application/x-www-form-urlencoded.
Make sure you've configured the urlencoded() body-parsing middleware in your Express app.
app.use(express.urlencoded());
Make sure you check for errors and actually send a response from your /apple_auth Express route
const { code, id_token, state, user, error } = req.body;
if (error) {
return res.status(500).send(error);
}
try {
const appleRes = await appleSignin.verifyIdToken(id_token, {
audience: "<client_id>",
ignoreExpiration: true,
});
// do something with the Apple response, then send a response
res.send(appleRes.sub);
} catch (err) {
console.error(err);
res.sendStatus(500); // send a 500 response status
}

HttpOnly Cookies not set to browser (client side with laravel)

NEED YOUR EXPERIENCE AND KNOWLEDGE.
im building a web application where client-side using Laravel 8 (fckid.test using valet or http://localhost:8000) and Node.js (http://localhost:3000) for server-side API.
Serverside using JWT for authorization and sending the token through Httponly Cookie.
My problem is when testing it with Postman and ThunderClient, it works perfectly as shown by picture below
here is on postman
here is how my backend looks like
1.Router
const usersController = require('../controllers/users.controller')
const express = require('express')
const router = express.Router()
router.post('/login', usersController.login)
The Controller
exports.login = (req, res, next) => {
const data = {
email: req.body.email,
password: req.body.password
}
console.log('Request made by :', req.headers)
usersService.login(data, (error, results) => {
if (error) {
console.error(error)
res.status(error.statusCode).send({
msg_head: "error",
msg_body: "Internal Server Error",
data: error
})
} else if (results.status) { //if status = true
console.log(results)
res
.status(results.statusCode)
.cookie("accessToken", results.accessToken, {
httpOnly: true,
path: '/',
sameSite: 'none',
domain: 'fckid.test'
})
.send({
msg_head: "success",
msg_body: results
})
} else {
console.error(results)
res.status(results.statusCode).send({
msg_head: "error",
msg_body: results
})
}
})
}
this is how my laravel client-side looks like
route (im using web.php route)
Route::post('/auth-login', [LoginController::class, 'login'])->name('auth-login');
LoginController
class LoginController extends Controller
{
public function login(Request $request)
{
$body = $request->post();
$response = Http::post('http://localhost:3000/users/login', $body);
if ($response->status() == 200) {
return redirect()->route('view_student');
// return $response;
} else {
return redirect()->route('login.page');
}
}
}
what i already tried are
give exception to this route to VerifyCsrfToken.php
making request from Microsoft Edge, Firefox and Chrome
but those attempts still give me the same result where httponly cookie is not set to the browser.
I try to $request->getHeaders() and it igives me correct response where i taught that the server already send correct information as expected.
on browser it looks like laravel overide the results from server
it's been 2 days looking for some information that can help me to solve this problem on youtube, google, but no one really talk about this. hope you guys could help me out of this stuck.
Thanks
Cannot send "httponly" value as false by PHP after new browser updates.
If you need to use your cookie on the JS side, set your cookie on the client side with document.cookie.
document.cookie = "test1=Hello; SameSite=None; Secure";

Stripe checkout session not working nodejs

I'm trying to implement a stripe checkout with NodeJs and React and it seems to get this error from the console log :
Access to XMLHttpRequest at 'https://checkout.stripe.com/pay/cs_test_a1y3gwvPAqpqhArQ9B83g3Du9EvF87HVcxzcx5Opt6o1rKinGEQViuXIq' (redirected from 'http://localhost:5000/api/account/subscriptioncheckoutsession') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here is what I have done so far:
env file
REACT_APP_FETCH_URL=http://localhost:5000
server.js
var whitelist = ['http://localhost:3000', 'http://localhost:5000'];
app.use(cors({ credentials: true, origin: whitelist }));
Route api
const YOUR_DOMAIN = 'http://localhost:5000';
router.post('/account/subscriptioncheckoutsession', async (req, res) => {
const session = await Stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'Product Subsciption',
images: ['https://i.imgur.com/EHyR2nP.png'],
},
unit_amount: 100,
},
quantity: 1,
},
],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/cancel.html`,
});
res.redirect(303, session.url)
})
Client
const handleCheckout = async (e) => {
e.preventDefault()
const response = await Axios.post(process.env.REACT_APP_FETCH_URL + '/api/account/subscriptioncheckoutsession')
}
<div className="form-group">
<button type="submit" className="btn btn-primary" onClick={handleCheckout}>
Subscribe or Renew Subscription</button>
</div>
What have I missed? Many thanks in advance and greatly appreciate any helps. Thanks again.
If you're using an XMLHTTPRequest(e.g., Axios.post) to get the URL for the CheckoutSession, it's not possible to redirect directly from the sever that way! That approach only works if you're doing what is described in Stripe's docs — calling that backend /subscriptioncheckoutsession route from the action of a <form> : https://stripe.com/docs/billing/subscriptions/checkout#create-session
So you could change your code to just have the form action be that route, and get rid of the onClick function and that usage of Axios entirely!
If you don't want to use a form submission and instead want to use an Ajax approach like Axios, what I'd suggest instead is your backend should just return the URL as part of the response body (e.g. res.json({"url":session.url}; instead of res.redirect) and do the redirect in JavaScript by setting window.location (e.g. let response = await axios.post(...); window.location = response.data.url)
I was using FastAPI and React to do the same, and ended up using the form action type. I tried using Fetch, but you cannot use the window.location = response.url method, or at least in TypeScript.
These are the parameter types in FastAPI and pip install python-multipart
session_id: str = Form(...)
price_id: str = Form(...)

How to parse signedCookies in express?

I send a cookie to my front-end thanks to express:
// signing the token
static generateToken(user: PartialUser): Cookie {
return jwt.sign({ _id: user._id }, process.env.JWT_TOKEN_KEY, {
expiresIn: "14d",
});
// sending the cookie
return res
.status(200)
.cookie("myApp", token, {
expires: new Date(Date.now() + msPerDay * 14),
httpOnly: true,
secure: true,
})
.json({ user });
// initializing cookie parser in index.js:
app.use(cookieParser(process.env.JWT_TOKEN_KEY));
//parsing the cookie
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const { myApp } = req.signedCookies;
if (req.signedCookies) {
return jwt.verify(
myApp,
process.env.JWT_TOKEN_KEY,
(error, parsedToken) => {
if (error) {
return res.sendStatus(403);
}
req.cookie = { _id: parsedToken._id };
return next();
}
);
}
return res.sendStatus(401);
};
The req.signedCookies object is always empty. So all my routes return a 403 - forbidden access. However, if I don't specify secure: true when sending the cookie, it works, because I can access it in req.cookies. The network tab shows that the cookie is correctly send along the client request.
How to fix this?
ps: I'm fine with using req.cookies, but my express server is hosted on Heroku and it never sends the cookie to the client, which is a custom https domain. This is why I'm trying the secure:true option. For now, it only works in localhost. Maybe the solution is elsewhere?
A cookie signature on one hand, and the secure option on the other hand, are actually two different things.
The secure option restricts the cookie to be sent over https only. This is intended at avoiding eavesdropping over the network. Incoming cookies that are set as secure will by default always be exposed on req.cookies by cookie-parser.
A cookie signature on the other hand is basically a cryptographic hash that is intended at making a cookie tamper-proof. It seems that with the cookie-parser package, you sign a cookie with the signed: true option. Only incoming cookies that have been explicitly signed will be exposed on req.signedCookies. Note this is all regardless of the secure option.

Getting session cookie data on initial page load following a redirect from a server (Node and React)

I am trying to build a token system to allow authentication via an email link. The flow I am thinking might work is...
click email link (of the form site.com/login?token=hf74hf64&email=m#email.com) -> server checks the token is valid and the email is registered -> server redirects to '/' with a session cookie -> client acknowledges session cookie and authenticates the user
The last step is where I'm having trouble. How do I detect from within my component that a session cookie is present?
I was thinking of something like this in my React auth component:
class AuthenticatedComponent extends Component {
componentWillMount() {
if (cookie) {
this.props.dispatch(authenticateUser())//.....
}
}
}
Might this work, or do I need to make a separate fetch to the server and trigger the dispatch depending on the response?
We've implemented a very similar approach for our app. For this to work, we handle all the login in Node and not in the actual components.
Check if token is provided in query string
Pass token to server to validate
If token is valid, create the cookie as you would for a normal user/pass login
Redirect call to original url, sans the token
server.js
// I abstracted the login functionality into one call since it's the same for us
var handleAuthRequest = function handleAuthRequest(auth_body, req, res, next) {
request ({
method: 'POST',
uri: Constants.API_LOGIN_URL,
body: auth_body,
json: true
}, (error, response, body) => {
if (response.statusCode === 200) {
// this makes a cookie with the response of the body (auth token)
ssoUtils.generateCookies(body, res)
// this redirects to the initial url, with the provided cookie.
// Assuming your router already doesn't allow certain components to be accessed
// without a cookie, it would work the same for the login_token auth too.
res.redirect(req.url)
}
else {
next();
}
})
}
// this needs to come before any other routes
app.use((req, res, next) => {
// check if login_token query string was provided
if (req.query.hasOwnProperty('login_token')) {
var {login_token} = req.query
// API call to server to validate token
var jwtToken = jwt.sign({
sub: login_token
}, Constants.API_JWT_SECRET)
// modify the redirect url to remove the token
let parsed = url.parse(req.url)
delete req.query['login_token']
let newUrl = parsed.pathname + '?' + qs.stringify(req.query)
req.url = newUrl
// call the generic login handler
return handleAuthRequest({link_token: jwtToken}, req, res, next)
}
Assuming your server will return the same response from logging in or a valid link token, this would just redirect the call back to whatever your existing process is so no separate functionality client side is needed. As you can see, we also sign the token in a JWT to ensure it's only accepted by the server if sent from our app.
We use React Router to handle our client side routing. Your onEnter check for the initial route would look like this.
routes.js
// token is passed in from our cookie by both the client and server
module.exports = function (token, userAgent, originalUrl) {
function isToken() {
return token !== undefined && token !== null;
}
function ifNoTokenRedirect(nextState, replaceState) {
// check if token is set from cookie
if (!isToken()) {
replaceState({ nextPathname: nextState.location.pathname}, '/signup? redirect=' + originalUrl.pathname);
}
}
return (
// the actual routes
)
}

Resources