Set Authorization header in RTK Query with Next Auth - next.js

I am currently using Next Auth to do authorization with Active Directory. I need to use the access token I receive and use it in the Authorization header. The problem is you can't use the useSession hook as it is not a react component. Not sure what kind approach I should take.
Here's where I'm trying to add the token:
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BASE_URL}`,
prepareHeaders: (headers, {
getState
}) => {
headers.set('Authorization', `Bearer ${token}`)
}
return headers;
},
});
I know token is undefined. Its only there to show where I need it.
I think I need to use redux, but I am not sure how to set the access token if the login happens on the Microsoft SSO page.
I hope this made sense.

Related

Handle Firebase client-side token and access to protected pages

I'm using Firebase auth to login with Facebook, Google and email/pass. Basically, everything runs client-side, I make a call to Firebase and I receive an object containing an access token (that is a JWT ID Token), a customer id and its email. When I get this object, I put it into a persistent store (local storage, I know it's bad) and I perform an API call to one of my sveltekit endpoint that will in turn make another API call to a backend API (written in Go) to get all the user informations: firstname, lastname, phone, address, stats etc. To give a little bit of context, below is a diagram to illustrate what's happening when a user sign-in using Facebook.
Up to now, I just put the Firebase object into a store and just check if the information are there to allow access to a particular page. This check is done in the +layout.svelte page of the directory containing the page to be protected. It looks like something like this:
onMount(() => {
// redirect if not authenticated
if (browser && !$authStore?.uid) goto(`/auth/sign-in`);
});
It's probably not a good thing especially since my store persists in the local storage and therefore is prone to some javascript manipulation.
From my understanding, there's at least 2 things that could be better implemented but I may be wrong:
Set the access token in an httponly cookie straight after receiving it from Firebase. This would avoid storing the access token (that is a JWT) in the local storage and It could be used to authorize access or not to some protected pages...
I receive the Firebase authentication object on client-side buthttp only cookie can only be created from server side. I thought about sending the object as a payload to a POST sveltekit endpoint (like /routes/api/auth/token/+server.js) and set the cookie from here but apparently cookies is not available in a sveltekit endpoint.
So, how and where should I set this cookie ? I see that cookies is available in the load function of a +layout.server.js file, as well as in the handle function of a hooks.server.js file, but I don't see the logic here.
Populate locals.userwith the authenticated user once I've performed a call to my backend. Well, here, it's not obvious to me because I think point 1) would be enough to manage access to protected pages, but I see that a check of locals.user is something I've seen elsewhere.
I tried to set locals.user in the sveltekit endpoint that is making the API call to the backend API:
// /routes/api/users/[uid]/+server.js
import { json } from "#sveltejs/kit";
import axios from "axios";
import { GO_API_GATEWAY_BASE_URL } from "$env/static/private";
export async function GET({ request, locals, params }) {
try {
const config = {
method: "get",
baseURL: GO_API_GATEWAY_BASE_URL,
url: `/users/${params.uid}`,
headers: {
Authorization: `Bearer ${uidToken}`, // <-- the Firebase ID Token
},
withCredentials: true,
};
const res = await axios(config);
// set locals
locals.user = json(res.data); // <--- DOESN'T SEEM TO WORK
return json(res.data);
} catch (error) {...}
}
...but in the +layout.server.js page that I've created I see nothing:
// routes/app/protected_pages/+layout.server.js
import { redirect } from "#sveltejs/kit";
export function load({ locals }) {
console.log(locals); // <----- contains an empty object: {}
if (!locals.user) throw redirect(302, "/auth/sign-in");
}
Thank you so much for your help

How do I securely get an Id_Token for a user securely through Google Credentials? I'm worried a user could change the URL (Using Expo Go/React Native)

Hello! I am OCD about security for my app and was wondering how to properly login/signup a user with the Google auth provider.
I have a client ID and secret for that client ID - from Google Credentials - for my app. I know not to put the secret in the client.
The code below works perfectly but I'm unsure if it's safe to generate an id_token for a user directly without any server code because of this doc from Expo Go:
Notice it says "be sure that you don't directly request the access token for the user". I don't know what this means exactly.
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId:
"my-client-id-goes-here.google.apps.com",
});
React.useEffect(() => {
if (response?.type === "success") {
const { id_token } = response.params;
const credential = new GoogleFirebase.GoogleAuthProvider.credential(
id_token
);
}
}, [response]);
Any ideas on how to execute this securely to make sure a user can't change the URL redirect parameters from Google or anything? I'm just not 100% on what I'm doing here.
Use an .env file to store your sensitive information. For example in your .env file put
CLIENTID="my-client-id-goes-here.google.apps.com"
Then call it like you did above:
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId:
process.env.CLIENTID,
});

How to verify the request is coming from my application clientside?

I have an app where users can create posts. There is no login or user account needed! They submit content with a form as post request. The post request refers to my api endpoint. I also have some other api points which are fetching data.
My goal is to protect the api endpoints completely except some specific sites who are allowed to request the api ( I want to accomplish this by having domain name and a secure string in my database which will be asked for if its valid or not if you call the api). This seems good for me. But I also need to make sure that my own application is still able to call the api endpoints. And there is my big problem. I have no idea how to implement this and I didn't find anything good.
So the api endpoints should only be accessible for:
Next.js Application itself if somebody does the posting for example
some other selected domains which are getting credentials which are saved in my database.
Hopefully somebody has an idea.
I thought to maybe accomplish it by using env vars, read them in getinitalprops and reuse it in my post request (on the client side it can't be read) and on my api endpoint its readable again. Sadly it doesn't work as expected so I hope you have a smart idea/code example how to get this working without using any account/login strategy because in my case its not needed.
index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home(props) {
async function post() {
console.log(process.env.MYSECRET)
const response = await fetch('/api/hello', {
method: 'POST',
body: JSON.stringify(process.env.MYSECRET),
})
if (!response.ok) {
console.log(response.statusText)
}
console.log(JSON.stringify(response))
return await response.json().then(s => {
console.log(s)
})
}
return (
<div className={styles.container}>
<button onClick={post}>Press me</button>
</div>
)
}
export async function getStaticProps(context) {
const myvar = process.env.MYSECRET
return {
props: { myvar },
}
}
api
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
const mysecret = req.body
res.status(200).json({ name: mysecret })
}
From what I understand, you want to create an API without user authentication and protect it from requests that are not coming from your client application.
First of all, I prefer to warn you, unless you only authorize requests coming from certain IPs (be careful with IP Spoofing methods which could bypass this protection), this will not be possible. If you set up an API key that is shared by all clients, reverse engineering or sniffing HTTP requests will retrieve that key and impersonate your application.
To my knowledge, there is no way to counter this apart from setting up a user authentication system.

How to reconcile Firebase Auth token refreshing with Server-Side Rendering

We're using Firebase in a Next.js app at work. I'm new to both, but did my best to read up on both. My problem is more with Firebase, not so much with Next.js. Here's the context:
In the client app, I make some calls to our API, passing a JWT (the ID token) in an Authorization header. The API calls admin.auth().verifyIdToken to check that the ID token is fresh enough. This works fine, since I am more or less guaranteed that the ID token gets refreshed regularly (through the use of onIDTokenChanged (doc link)
Now I want to be able to Server-Side Render my app pages. In order to do that, I store the ID token in a cookie readable by the server. But from here on, I have no guarantee that the ID token will be fresh enough next time the user loads the app through a full page load.
I cannot find a server-side equivalent of onIDTokenChanged.
This blog post mentions a google API endpoint to refresh a token. I could hit it from the server and give it a refresh token, but it feels like I'm stepping out of the Firebase realm completely and I'm worried maintaining an ad-hoc system will be a burden.
So my question is, how do people usually reconcile Firebase auth with SSR? Am I missing something?
Thank you!
I've had that same problem recently, and I solved by handling it myself. I created a very simple page responsible for forcing firebase token refresh, and redirecting user back to the requested page. It's something like this:
On the server-side, check for token exp value after extracting it from cookies (If you're using firebase-admin on that server, it will probably tell you as an error after verifying it)
// Could be a handler like this
const handleTokenCookie = (context) => {
try {
const token = parseTokenFromCookie(context.req.headers.cookie)
await verifyToken(token)
} catch (err) {
if (err.name === 'TokenExpired') {
// If expired, user will be redirected to /refresh page, which will force a client-side
// token refresh, and then redirect user back to the desired page
const encodedPath = encodeURIComponent(context.req.url)
context.res.writeHead(302, {
// Note that encoding avoids URI problems, and `req.url` will also
// keep any query params intact
Location: `/refresh?redirect=${encodedPath}`
})
context.res.end()
} else {
// Other authorization errors...
}
}
}
This handler can be used on the /pages, like this
// /pages/any-page.js
export async function getServerSideProps (context) {
const token = await handleTokenCookie(context)
if (!token) {
// Token is invalid! User is being redirected to /refresh page
return {}
}
// Your code...
}
Now you need to create a simple /refresh page, responsible for forcing firebase token refresh on client-side, and after both token and cookie are updated, it should redirect user back to the desired page.
// /pages/refresh.js
const Refresh = () => {
// This hook is something like https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
const { user } = useUser()
React.useEffect(function forceTokenRefresh () {
// You should also handle the case where currentUser is still being loaded
currentUser
.getIdToken(true) // true will force token refresh
.then(() => {
// Updates user cookie
setUserCookie(currentUser)
// Redirect back to where it was
const decodedPath = window.decodeURIComponent(Router.query.redirect)
Router.replace(decodedPath)
})
.catch(() => {
// If any error happens on refresh, redirect to home
Router.replace('/')
})
}, [currentUser])
return (
// Show a simple loading while refreshing token?
<LoadingComponent />
)
}
export default Refresh
Of course it will delay the user's first request if the token is expired, but it ensures a valid token without forcing user to login again.

Login app with Redux & ReactRouter

I would be thankful if someone could point me into a right direction in understanding the Redux architecture.
I should implement "reducer" functions that will handle my actions.
Reducer functions should be combined and create a store.
Lets say I have a LoginForm (React) component, that makes a XHR request to backend API, and receives a JWT token in response.
When I get the response from the API I should dispatch an action like:
store.dispatch({type: "USER_LOGGED_IN",
payload: {username: "john", JWT: "..."});
This updates the state of my application.
What next?
How do I route to to next page? How do I rerender my components (like navbar, etc.) with the logged in username?
Do I use listeners for that?
Let's say you've a method to authorize user:
import { browserHistory } from 'react-router';
// ...
function promisedApiCall(inputData) {
// ...
// api request to backend with input data
// return a promise
}
/*
* on form submit we call this with input data
*/
function authorizeUser(inputData) {
return promisedApiCall(inputData)
.then((response) => store.dispatch({
type: "USER_LOGGED_IN",
payload: {
username: response.userName,
JWT: response.JWT
}
}))
.then(() => browserHistory.push('/success/path/url'))
.catch(() => browserHistory.push('/failure/path/url'));
}
Assuming you have the following prerequisites:
Created redux store and store object is available in the scope where authorizeUser() is executed.
The method promisedApiCall is the function which makes the request to backend with input data from LoginForm.
promisedApiCall should return a promise. [this is really important]
Configured react-router with redux
Once this is complete, app state is updated with user info and also user will be redirected to a new page. This post explains more about programmatically redirecting using react-router.
Access you app state in you component using Redux connect.
Now you have the user info in your component as props.
react-router has a component browserHistory.You can import that like this,
import {browserHistory} from 'react-router';
And to change your route,
browserHistory.push(<route_where_you want_to_go>);
This will let you change the route.

Resources