How do I forward a cookie using fetch in getServerSideProps? - reactjs

I'm using Nextjs for my app. On a page, I would like to fetch data from an authenticated API endpoint ('/api/profile').
I have tried the following, with no success:
export async function getServerSideProps(ctx) {
const { req, res } = ctx
const cookies = cookie.parse(req.headers.cookie ?? '')
const mycookie = cookies[MY_COOKIE] // mycookie exists and is set correctly
if (mycookie) {
const response = await fetch(process.env.SERVER_HOST+'/api/profile', {
credentials: 'same-origin' // I tried with and without this, also tried "include" instead
})
...
I have 2 questions:
Is there a way to avoid having to enter the absolute URL? (I was hoping to simply use '/api/profile', since it's an "internal" api)
How do I make sure the cookie required to fetch data from /api/profile is forwarded through fetch?
N.B: My cookie is httpOnly.

Turns out I'm allowed to manually forward the cookie through:
if (mycookie) {
const response = await fetch(process.env.SERVER_HOST+'/api/profile', {
headers: {
cookie: mycookie
}
})
...

if you use get server side props then the recommended way is to process whatever data fetching functions you have directly in getserversideprops.
calling fetch /api is redundant. what you can do is to extract the function from the api and use it directly in getserversideprops.
what you are doing now is
client -> serverside rendering -> api -> serverside rendering -> client
it can become
client -> serverside rendering -client

Related

SvelteKit - /routes/a/+page.server.ts fetch('/b/') url confusion, version #sveltejs/kit#1.0.0-next.512

When I try to fetch('/b/') within the load function of /routes/a/+page.server.ts it refuses to accept local URL references.
Instead of being able to do
/b/
I have to use url:
http://localhost:3000/b/
Because the fetch() call refuses to accept the url (error: "Failed to parse URL"). I'm trying to consume my own api to reuse code. I thought SvelteKit fetch was supposed to support these local routes for api calls?
The example in documentation: https://kit.svelte.dev/docs/routing
Shows +page.svelte calling url '/api/add/' from fetch(), but not from within +page.server.ts - is there some reason they would not allow the same convention?
Thank you.
SvelteKit developers got back to me and indicated that there are two choices of fetch(url) function.
// /src/routes/test/[param0]/[param1]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
// ERROR: this will fail with URL parsing
let fetchResult = fetch('/api/target/');
}
SvelteKit aware fetch passed as load function parameter:
export const load: PageServerLoad = async ({ params, fetch }) => {
// NOTE: fetch was passed in as a parameter to this function
let fetchResult = fetch('/api/target/');
}
Confusing!
When I have an internal API route I want to hit within my sveltekit application, I structure it as so:
├src
|├routes
||├api
|||├example
||||├+server.js
Now, elsewhere in the app, you can hit the route like so using the fetch api:
let res = await fetch("/api/example")
refer to this section of the SvelteKit docs for a better understanding:
https://kit.svelte.dev/docs/routing

What's the best way to store a HTTP response in Ionic React?

I'm developing an app with Ionic React, which performs some HTTP requests to an API. The problem is I need to store the response of the request in a local storage so that it is accessible everywhere. The way I'm currently doing it uses #ionic/storage:
let body = {
username: username,
password: password
};
sendRequest('POST', '/login', "userValid", body);
let response = await get("userValid");
if (response.success) {
window.location.href = "/main_tabs";
} else if (!response.success) {
alert("Incorrect password");
}
import { set } from './storage';
// Handles all API requests
export function sendRequest(type: 'GET' | 'POST', route: string, storageKey: string, body?: any) {
let request = new XMLHttpRequest();
let payload = JSON.stringify(body);
let url = `http://localhost:8001${route}`;
request.open(type, url);
request.send(payload);
request.onreadystatechange = () => {
if (request.readyState === 4 && storageKey) {
set(storageKey, request.response);
}
}
}
The problem is that when I get the userValid key the response hasn't come back yet, so even awaiting will return undefined. Because of this I have to send another identical request each time in order for Ionic to read the correct value, which is actually the response from the first request. Is there a correct way of doing this other than just setting timeouts everytime I perform a request?
You are checking for the results of storage before it was set. This is because your sendRequest method is calling an asynchronous XMLHttpRequest request, and you are checking storage before the sendRequest method is complete. This can be fixed by making sendRequest async and restructuring your code a bit.
I would suggest you instead look for examples of ionic react using hooks or an API library - like fetch or Axios. This will make your life much easier, and you should find lots of examples and documentation. Check out some references below to get started:
Example from the Ionic Blog using Hooks
Example using Fetch using React
Related Stack Overflow leveraging Axios

Next JS - Handling getInitialProps on _app.js in SSR vs CSR

I am trying to create a Next JS application that handles the authentication and initial routing inside getInitialProps. I discovered this method can be executed either in the server or on the client.
My approach so far it's to have 2 different handlers based on detecting if I am in executing in the server checking for the presence of the req attribute inside of ctx.
This does the trick but doesn't feel like is the right way of doing. Can somebody, please, tell me if there is a cleaner way.
All authentication is handled in a separate subdomain, so I just need to redirect to the auth subdomain if there is no cookie or auth request fails for some other reason.
import "../../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
MyApp.getInitialProps = async (appContext) => {
let cookie, user;
let ctx = appContext.ctx;
//Check if I am in the server.
if (ctx.req) {
cookie = ctx.req.headers.cookie
//Do auth request.
//Redirect base on user properties
// handle redirects using res object
ctx.res.writeHead(302, { Location: "/crear-cuenta"});
} else {
cookie = window.document.cookie;
//Do auth request.
//Redirect base on user properties
//Do redirects using client side methods (useRouter hook, location.replace)???
}
//Return pageProps to the page with the authenticted user information.
return { pageProps: { user: user } };
};
export default MyApp;
I think your code is clean enough. Of course you still can maintain it.
My suggestion would be as the followings:
MyApp.getInitialProps = async (appContext) => {
in this line you can use object destructuring technique to get the context straightforward:
MyApp.getInitialProps = async ({ ctx }) => {
then you won't need this line for example anymore : let ctx = appContext.ctx;
The most important part of your code which can be cleaned up by the way is the area that you have written your auth request twice in an if/else condition. I would suggest you to implement that part like this:
const cookie = ctx.req ? ctx.req.headers.cookie : window.document.cookie;
Although I would try to keep everything in getInitialProps on server side, In that case I make a small change to get the cookie as following and process it in server-side only.
const cookie = cookie.parse(ctx.req ? ctx.req.headers.cookie || "" : undefined);
Note that: I'm using a cookie parser which u can install the package yourself as well. (npm install cookie)
if you need to do an extra check on your cookie at client side, I will do that in componentdidmount or in case you are using react hooks in useEffect. But it is not necessary.
Now you can implement //Do auth request once, which will cause cleaner code and of course to reduce unnecessary repetition.

Nextjs + expressjs + Azure Web App : two factor authentication with express ('fs' can't be used on client side)

Stack : next.js/express.js/typescript/nodemon
I have a dependency on azure devops api which seems to be using 'fs' under the hood. So I can't use this library in any of the pages (including in getInitialProps).
I created a custom route (call it "get_data") in express server which provides me with the data. I call this route in getInitialProps of the page that will render the data (call it data.tsx) .
The whole app is behind two factor authentication in azure web apps. when get_data call is made in getInitialProps, I get a 401. Note that the origin is the same for serving the page and the get_data api.
Is there a way to pass current auth context when I make the get_data api call ?
In the express api, the method currently looks like this :
server.get('/get_data', (_req, res) => {
let ado = new azure_devops() // this uses the azure-devop-api package
ado.get_data().then((data) => {
res.setHeader('Content-Type', 'application/json');
res.json(data) // return data as json
})
});
Is there a way to merge the two (page and data serving) like the following (so I don't have to make extra api call with auth setup) ?
server.get('/data', (req, res) => { //note the change in route that serves the page data.tsx
const actualPage = '/data';
let ado = new azure_devops() // this uses the azure-devop-api package
ado.get_data().then((data) => {
res.setHeader('Content-Type', 'application/json');
res.write(data) // BUT this is where method returns instead i want to just add to the response body
app.render(req, res, actualPage); // THIS is not executed (so the page doesn't get rendered)
})
});
Appreciate any pointers.
Got the answer from this question.
Still making the API request from getInitialProps. I was missing adding cookie from the context.
const res = await fetch('http://' + context.req.headers.host + '/get_data', {
headers: {
cookie: context.req.headers.cookie, // WAS MISSING THIS
}
});

How To Setup Minimalist Authentication In Rails with React?

I am trying to set up a minimal layer of authentication between my Rails backend and my React front end, but I am running into some problems.
I cannot seem to find the cookie key value that the server passes down to my client. In the network tab, I see it in the response: Set-Cookie:_skillcoop_session=...., but when I use js-cookie to look for the above cookie, _skillcoop_session, I only see one called identity-token=... and its value is different from _skillcoop_session. How do I access _skillcoop_session in the browser?
What header key do I pass up to the server to signal to my backend to use 'this' header key to match up with the session it has stored off? In this post, Justin Weiss seems to suggest that I make the request to the server with a header like: Cookie: _skillcoop_session=....
Am I doing this all wrong? Would I be better off using a gem like devise?
Also in order to load the session in my other controllers, I have had to do something like session['init'] = true, and I learned to do this from this SO post. This seems hacky. Why do I have to manually reload the session in separate controller actions after I've set it previously in a different controller action in a different request?
I'm currently just stubbing out the user and the authentication -- all I want to do to get the plumping in place is set a session[:user_id] and be able to read that session data in other controller actions. For this I have two main files for consideration: UsersController and Transport.js. In UsersController I am just stubbing the session[:user_id] with the number 1 and in Transport.js I'd like to pass the cookie received from the server so that the backend can maintain a session between requests with a client.
Here is my controller:
class UsersController < ApplicationController
def create
session[:user_id] = 1
render json: user_stub, status: :ok
end
def show
puts "user id: #{session[:user_id]}"
# should return, 1, but is returning, nil...why?
render json: user_stub, status: :ok
end
private
def user_stub
{
id: 1,
email: params['email'] || 'fakeemail#gmail.com',
password: params['password'] || 'fake password'
}
end
end
Here is the main location of my app where I make my request to the server - it's in an abstraction I call Transport.js:
require('es6-promise').polyfill();
require('isomorphic-fetch');
var cookie = require('js-cookie');
const GET = 'GET';
const POST = 'POST';
function Transport() {
}
Transport.prototype.get = function(url, options = {}) {
return this.query(GET, url, null, options);
};
Transport.prototype.post = function(url, dataString, options = {}) {
return this.query(POST, url, dataString, options);
};
Transport.prototype.query = function(method, url, dataString, options = {}) {
var data;
if (dataString) {
data = JSON.parse(dataString);
}
switch(method) {
case GET:
return fetch(url, Object.assign({headers: {'Cookie': cookie.get('_skillcoop_session')}}, options, {
method: method
}));
case POST:
return fetch(url, Object.assign({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}, options, {
method: method
}));
default:
throw new Error("This HTTP Method is not supported.");
}
};
module.exports = Transport;
According to this SO post, one cannot access the Set-Cookie header in JS. Thus, I suppose my attempts to handle Set-Cookie in the response headers was a fools effort.
According to the NPM package that I'm using to make HTTP requests, I need to pass {credentials: 'same-origin'} key value pair in the second argument to fetch, which will 'automatically send cookies for the current domain'. That did the trick -- the session object is available and contains the user_id that was set in the session in the previous request in a different action.
Yes. I changed up how I approached this problem. I leaned very heavily on this Reddit post. In short, I use ruby-jwt on the backend and store the token in localStorage on the front end. Each request out to the server will include the token in a header AUTHORIZATION.
In following steps 1 and 2, it looks like I no longer have to 'reload the session'.

Resources