Use Firebase for Auth and MongoDB for DB - node.js

I'am building a react native mobile app with user authentication. I decided to use Firebase auth as it is relatively easy to implement.
I want to use my own backend (node, express) and my own DB (MongoDB) and I need to protect my API calls to check if the user is authenticated and if he has the permission to do the operation.
But I am not at ease with Token validation and I'am not sure I'am doing it the right way...
This is how I am trying to do to make a simple GET or POST request from the client (mobile app):
On the client I retrieve the current User token and send it in the header of my GET request:
firebase.auth().currentUser.getIdToken(true).then(token => {
fetch('http://localhost:3000', {
method: 'GET',
mode: 'cors',
headers: {
'AuthToken': token
}
}).then(res => res.json().then(res => console.log(res)))
.catch(err => console.log(err))
An on the server I use a middleware to check the token thanks to firebase admin
function checkAuth(req, res, next) {
if (req.headers.authtoken) {
admin.auth().verifyIdToken(req.headers.authtoken)
.then((token) => {
console.log(token)
next()
}).catch(() => {
res.status(403).send('Unauthorized')
});
} else {
res.status(403).send('Unauthorized')
}
}
So for a simple get request from a logged user I have to do the following :
get the user IdToken on client side
send it to the server
check it with firebase
do the operation on my DB
send the data back to the client
Am I doing it the right way ? I don't know if I should store the IdToken on the phone memory in order to avoid geting it from firebase everytime (but the token expires after 1 hour...)
It seems like a lot of "check from firebase" operation just for simple fetch...
Can you give me some advise on how to make all of this more efficient ?
Thanks a lot !

Overall, your flow looks fine to me, but there are a few things I'd optimize.
By passing true into getIdToken(true), you are forcing it to refresh the token each time before executing your fetch.
There is no need for that, as Firebase already automatically refreshes the ID token in the background. So you can make it less resource intensive, and quite a bit faster, with:
firebase.auth().currentUser.getIdToken().then(token => {
fetch('http://localhost:3000', {
...
After you call admin.auth().verifyIdToken(req.headers.authtoken), you get a result that also shows how long the token is valid for. So you could cache the verified token somewhere until it's close to that time, and use that cached version if you get the same input. That saves you on calls to verifyIdToken`.

Related

Spotify JWT Flow

Tech stack: NodeJS backend Angular10 front end
I am implementing the Spotify API into my application. I'm having some issues with the flow.
To start off I have no issues authenticating or retrieving a token, but I am having an issue figuring out what to do with it. Assuming we understand the Spotify flow and are on the same page there is an issue presented for client-side rendered apps and that is how to get our JWT into the storage.
The reason this is an issue is because of the flow Spotify constrains us to, it requires a callback where the token is passed in as a query param.
This callback is on my backend NodeJS server, the endpoint is functioning fine but this issue starts here - I cannot pass in my user_id or any other user information it simply returns a JWT at this point the only thing I can do is send a template to the client but it's of a different domain.
Option (1)
Somehow use the Iframe hack to set a storage item in my other tab that's running my front end; I do not like this cause it's hacky and does not feel the right way to handle this.
Option (2)
Perform some kind of hack that maps user ids from the initial request and maps the correct user id to the correct token and then sending it to the client somehow ( database then GET ) I also don't like this it's hacky
What we don't want is to keep the client secret and other credentials on the front end like some examples I have seen, this is fine for personal stuff but not real apps for very obvious reasons.. All client secrets and passwords should be on the backend never sent to the client only their user JWT
So the issue here summarized, How do I return the JWT to the client without doing something hacky? the code below returns this to the client but brings us to option (1) above and obviously returns a template style which is undesirable cause I have a client-side rendered app!; I want something clean and professional, thank you.
router.get('/musicAuthCallback', function (req, res) {
const spotifyCode: string = req.query.code;
if (production.ROUTE_LOGGING) {
routeLogger.info('Spotify endpoint callback hit');
}
const _spotifyService = new SpotifyService();
_spotifyService.tradeSpotifyAuthToken(spotifyCode)
.then((jwt: any) => {
if (jwt === false) {
res.send({ "message": "Unable to get JWT for Spotify", "status": REQUEST_FAILED, "title": "Authentication Error" });
} else {
const template = `<h1> Success! </h1> <script>
var main=window.open("http://localhost:4200/");
main.setItem('spotify_jwt', ${jwt});
</script>`
res.send(template)
}
})
.catch((error) => {
logger.error(error);
res.send({ "message": "Something went really wrong..", "status": REQUEST_FAILED, "title": "Please Try Again.." });
});
})
This is silly but kind of illusive until you poke around the docs more.. the docs didnt make it clear to me I could use "state" as a query param.. in this i was able to pass the users ID into here so &state=600 and this is passed into the users callback URL which makes it possible to attach a user to a specific JWT given by Spotify
https://accounts.spotify.com/authorize?client_id=5fe01282e94241328a84e7c5cc169164&redirect_uri=http:%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&response_type=token&**state=123** <- this can be what you want! and can retrieve it in your callback URL sitting on your NodeJS server like this..
const spotifyCode: string = req.query.code; // jwt
const userID: string = req.query.state; // value you passed in

How to make HTML auth form and JSON Web Tokens communicate together in Ionic/Angular

I'm working on an Ionic application.
On the one hand I have an auth basic form in which people fill in their username and password. On the other hand I'd like to implement authentification with JSON Web Tokens and Node JS.
The workflow would be this one : as soon as a user fills in his credentials, they will be sent with a POST request. If these credentials are correct, the user can access to the application and gets an access token as a response.
The thing is that I'm a little bit lost with all that concepts. I built a form and sent informations with a POST request. I managed to create some APIs with Node JS and that's ok. I see how to build a authentified webservice too (e.g : https://github.com/jkasun/stack-abuse-express-jwt/blob/master/auth.js).
But I concretely don't understand the links between the html form and the authorisation check part..
To be clearer, how is it possible to make the html part and the Node JS scripts communicate together ?
Before posting that question I made many researches and found many stuff on building an authentified API. But there was very few advice on how to make it communicate with the client part (I mean the form), which is what I have to do.
If anyone has any ressources (document, Github examples..) on that, I'll greatly appreciate. But I would be very happy too if someone try to make me understand these concepts. I guess I have to improve my knowledge on all that so that I could test some POCs.
Many thanks in advance !
JWT General flow:
1- Authenticate using a strategy (You done it)
2- Deliver an accessToken along with response (You done it)
3- The client MUST store this accessToken (LocalStorage is the best place, not cookies: They are vulnerable to csrf attacks)
4- On every request you are going to make to a protected area (where user is supposed to be authenticated and authorized), make sure to send you accessToken along with it, you can put it on Authorization header, a custom header, directly in body of the request... Basicaly just make sure to send it properly.
5- On the server receiving client requests, you NEED to verify that token (You verify it by checking the signature of the accessToken).
6- If he is authorized, great, if not, send back an HTTP Unauthorized Error.
Here is my implementation using an accessToken on a header + passportjs-jwt:
Client code
To store token:
localStorage.setItem('accessToken', myAccessToken);
To send it:
const myAccessToken = localStorage.getItem('accessToken');
{
headers: {'Authorization', `Bearer ${myAccessToken}`}
}
Server code
1- Configure passport
passport.use('jwt', new JwtStrategy({
jwtFromRequest: jwtPassport.ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: myAccessTokenSecret,
passReqToCallback: true
}, (req, payload, done: (err?, user?) => void): void {
User
.findOne({where: {id: req.params.id}})
.then((user: User) => {
if (!user) {
return done(new Error(`No user found with id: ${req.params.id}`), null);
}
return done(null, user);
})
.catch((e: Error) => done(e, null));
}));
Pay attention to callback: If your callback is called, it means that passport has successfuly verified the token (It is valid). In my example, i get the user details in database and this is the user that will be returned and put in req.user object passed to my controller below:
2- Finally, the controller route (protected area):
.get('/users/:id', passport.authenticate('jwt'), (req, res, next) => {
// do stuff in protected area.
}
And that's it. If you want more security, check refreshTokens implementation.
I used passport because i found it relevant in my case, but you can write your own handler, by using jsonwebtoken and just calling its "verify" function.
You can find documentation of passport jwt strategy here => http://www.passportjs.org/packages/passport-jwt/

Xero Api NodeJS - invalid grant issue

I am using the xero-node module to create an app for Xero with NodeJS.
For some reason every single request for a refresh token is coming back as invalid grant
i have been taken the code and made the callback attempt to grab the refresh straight after i do the auth so I can ensure thats its the latest token and still does the same thing.
Code is below this method is called when Xero passes back to the app (callbackURL)
The error i get is "invalid_grant" it does not give any other errors and there is no errors logs in Xero so very unhelful.
exports.callback = async function (req, res) {
const tokenSet = await xero.apiCallback(req.url);
try {
const newTokenSet2 = await xero.refreshWithRefreshToken('ClientID, 'ClientSecret', tokenSet.refresh_token);
}
catch(error){
console.log(`ERROR refresh: \n ${JSON.stringify(error.response.body, null, 2)}`);
};
///console.log(tokenSet);
};
Any ideas ?
Without seeing more of your code for context it's bit tough to tell. I'm assuming you're not actually sending 'ClientID' and 'ClientSecret' as strings.
I would refer you here for refreshing with a fully initialized client leveraging openid-client:
https://github.com/XeroAPI/xero-node-oauth2-app/blob/f1fbd3a08e840e54e8ce57f7050ddde6686208d8/src/app.ts#L233
Or here, to initialize an empty client and refresh by passing the client, secret, and refresh_token:
https://github.com/XeroAPI/xero-node-oauth2-app/blob/f1fbd3a08e840e54e8ce57f7050ddde6686208d8/src/app.ts#L236
I think what's happening is that you're using the second method but with an already initialized client. Again, tough to tell with limited context. If this doesn't resolve it please post more detail.

How to implement authentication in Next.js

I am new to Next.js and I am struggling with the authentication system using jwt token. I want to know what is the best / standard way to store the jwt token and routing with the authentication system. I have been trying different approaches, from different tutorials/articles, but do not quite understand it. Here are what I have tried.
When the user login, it sends username/password to a separated API server (ex. new project that handles backend stuff), the server will respond with the access-token, then in Next.js project, I set the cookie with that received token. In Next.js project, protected routes will be wrapped with a withAuth hoc, which will check for the token in a cookie. The problem with this approach is that it is vulnerable to XSS because the cookie has no httpOnly flag.
This is similar to 1.) but using localStorage, the problem is access-token could not be sent to the server on the first request. (This one I'm not sure, but in my understanding, in every HTTP request, I must stick my access-token manually, so what about requests that I have no control over? ex. first request or using <a> tag).
I wrote authentication backend inside Next.js server (custom express server). When the user login, the server will validate it and then set an httpOnly cookie. Then the problem is, with client-side routing (go to URL using Next.js Router), it could not check for token. For example, if a page is wrapped with withAuth hoc, but it cannot access the token inside cookies with javascript.
And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage, then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to the server in every client-side page change?
Since we are on quarantine I have enough time to answer this question. It will be a long answer.
Next.js uses the App component to initialize the pages. _app page is responsible for rendering our pages. We authenticate users on _app.js because anything that we return from getInitialProps can be accessed by all of the other pages. We authenticate user here, authentication decision will be passed to pages, from pages to header, so each page can decide if the user is authenticated or not. (Note that it could be done with redux without prop drilling but it would make the answer more complex)
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
const user = process.browser
? await auth0.clientAuth()
: await auth0.serverAuth(ctx.req); // I explain down below
//this will be sent to all the components
const auth = { user, isAuthenticated: !!user };
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps, auth };
}
render() {
const { Component, pageProps, auth } = this.props;
return <Component {...pageProps} auth={auth} />;
}
}
If we are on the browser and need to check if a user is authenticated, we just retrieve the cookie from the browser, which is easy. But we always have to verify the token. It is the same process used by browser and server. I will explain down below. But if we are on the server. we have no access to the cookies in the browser. But we can read from the "req" object because cookies are attached to the req.header.cookie. this is how we access to cookies on the server.
async serverAuth(req) {
// console.log(req.headers.cookie) to check
if (req.headers.cookie) {
const token = getCookieFromReq(req, "jwt");
const verifiedToken = await this.verifyToken(token);
return verifiedToken;
}
return undefined;
}
here is getCookieFromReq(). remember we have to think functional.
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
Once we get the cookie, we have to decode it, extract the expiration time to see if it is valid or not. this part is easy. Another thing we have to check is if the signature of the jwt is valid. Symmetric or asymmetric algorithms are used to sign the jwt. You have to use private keys to validate the signature of symmetric algorithms. RS256 is the default asymmetric algorithms for APIs. Servers that use RS256, provide you with a link to get jwt to use the keys to validate the signature. You can either use [jwks-rsa][1] or you can do on your own. You have to create a certificate and then verify if the token is valid.
Assume that our user authenticated now. You said, "And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage,". We use protected routes to give access only to the authorized users. In order to access those routes, users have to show their jwt tokens and express.js uses middlewares to check if the user's token is valid. Since you have seen a lot of examples, I will skip this part.
"then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to a server in every client-side page changing?"
with verifying token process we are 100% sure if the token is valid or not. When a client asks the server to access some secret data, the client has to send the token to the server. Imagine when you mount the component, component asks the server to get some data from the protected routes. The server will extract the req object, take the jwt and use it to fetch data from the protected routes. Implementation of the fetching data for browser and server are different. And if the browser makes a request, it just needs the relative path but the server needs an absolute path. As you should know fetching data is done getInitialProps() of the component and this function executed on both client and server. here is how you should implement it. I just attached the getInitialProps() part.
MyComponent.getInitialProps = async (ctx) => {
const another = await getSecretData(ctx.req);
//reuslt of fetching data is passed to component as props
return { superValue: another };
};
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
export const getSecretData = async (req) => {
const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
};
[1]: https://www.npmjs.com/package/jwks-rsa
With the introduction of Next.JS v8, there are examples placed in the NextJS example page. The basic idea to follow is:
JWT
Using cookies to store the token (you may choose to further encrypt it or not)
Sending the cookies as authorization headers
OAuth
Using a third-party authentication service such as OAuth2.0
Using Passport
This question might need an updated answer, now middlewares are there in Next.js 12 (october 2021): https://nextjs.org/docs/middleware
I am drafting a comprehensive answer to explain auth in Next.js more deeply, you can follow the progress there on GitHub
Here I'll try to propose a summary for Next.js, using middlewares.
Verifying the token after auth and redirecting accordingly
Most of the answer from #Yilmaz from april 2020 is still relevant. However, previously, we had to use getInitialProps in _app to process the request OR a custom server.
This is no longer the case.. Using a middleware let's you achieve a similar purpose, with cleaner code. Because middleware are specifically designed for such use cases.
Here, I suppose you get a JWT access token using an asymetrical algorithm like RS256, exactly like in this previous answer.
Here is a possible implementation:
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
const removeCookie = (res: NextResponse, cookieName: string) => {
res.headers.append("Set-Cookie", `${cookieName}=; Max-Age=-1; Path=/`);
return res;
};
export default async function middleware(
req: NextRequest,
ev: NextFetchEvent
) {
const { pathname } = req.nextUrl;
const isPublic = isPublicRoute(pathname);
if (isPublic) {
return NextResponse.next();
}
const accessToken = req.cookies[TOKEN_PATH];
if (!accessToken) {
return NextResponse.redirect(LOGIN_HREF);
}
const isValidToken = await checkAccessToken(accessToken);
if (!isValidToken) {
let res = NextResponse.redirect(LOGIN_HREF);
res = removeCookie(res, TOKEN_PATH);
return res;
}
return NextResponse.next();
}
How to verify the token
In my example, the checkAccessToken should verify the token (not decode, verify the signature).
This is where things are the most complicated imo.
When using the RSA256 algorithm
You also get a PUBLIC certificate (in addition to the SECRET key that must be... kept secret). Eventhough you do the check in the middleware, which is private and server-only code, that's good news because it means you could even use it in the browser, in theory.
So, you can either fetch the token validation endpoint provided by your auth server, or verify the token yourself.
Fetching is not the recommended option because it might break Vercel/Next edge capabilities and add latency, according to the documentation.
I must admit that I did not succeed to verify the token yet using Next.js :) I'll update this answer if I manage to have a code sample that works.
When using a symmetrical encryption
You have only a PRIVATE secret passphrase. It means that the decoding have to happen server-side (good news, you are writing a middleware).
Login/logout
This doesn't change with middlewares. You store your access token as an httpOnly cookie. When logging out, you unset this cookie.
Managing those Set-Cookies headers are the responsibility of your auth server.
This is a basic workflow but it should work. You can then add a refresh token in the mix with a similar approach.
About token revokation
If you verify the token in your middleware, there is no immediate revokation mechanism for the access token. Because there is no call to a database.
Therefore, in this scenario, you'd want to opt-in for short lived access token (eg 5 minutes) coupled with a refresh token. You can revoke the refresh token, so basically revoking works but takes a few minutes.
If a 3rd party server verifies the token: then it could check for blacklisted tokens.
Caveats
Also, some piece of advice: most articles, tutorials etc. online are focused on server-to-server communication. Or client-to-API. They completely suck when it comes to check authentication before accessing web pages.
For instance, setting the Authorization header is not possible in the browser. It works only when communicating with an API. Cookies are mandatory for web pages.
Even then, if this API is meant to be called from a browser, it should preferably accept a cookie.
When discussing with experts on the field, you need to always clarify the Next.js use case.
Open questions: about session-based authentication
Some frameworks seem to prefer relying on the database. They store a hashed token in the db, which acts as a session. If you want to check auth, you need a server that will check the user's token against the stored token (= checking that there is an active session with this token).
I am thinking of Meteor for instance.
I couldn't find the name of this mechanism and its actual relation to JWT however. Are they simply variations of the JWT approach?
Next.js official authentication doc is not showing middlewares at the time of writing, but instead use getServerSideProps. I really don't like this pattern.
It uses a kind of session system but I am not clear about the internals of it, I am not even sure of the name (is that session-based auth?).
Vercel edge handles examples shows how to secure an API route, but not a page (at the time of writing)

Authentication: sessions, JWT, or something else?

I have a web server that serves a website to users. Through this website they can sign in using oauth, which I currently store in a session on the server. The session basically just tells me what user this is and if they are signed in or not (which is all I want from it).
I then have an API-server which is only used to query for information which might be protected (ie, only signed in users can see it). The API-server is only accessible through the main server, which just proxies everything through. The API-server has access to the same backing store which means it can read the sessions and process the requests requiring authentication checks. A nit to this approach is: since the session only lives in memory until saved, I need to save it every time a request is going to be proxied to the server to ensure the API-server has fresh data.
But thats not the true issue, the true issue is when a user visits say /account, I want to prefetch all the data they need and build the entire site on the server before sending them it (so they don't have to wait for the http-request and render the entire site again). This is problematic, because when the server tries to access an area only authenticated users can see, it gets denied. To work around this I would need to inject the session id in the request and re-work a lot of code which intuitively feels like the wrong approach. If I use JWT, I could perhaps create a "super user token" that the server would generate which would allow it access to any part of the authenticated area, but that also feels risky.
What do you guys think?
Some additional information that might be relevant:
I'm using react and doing server side rendering
I am using redux (which is the state I try to prefill)
I am using Node
Thanks in advance!
Use a middle ware before controller in every secure route/endpoint
in this middle ware check your jwt token if token is valid then call next middleware.
router.route('/post')
.post(isAdmin,postCntr.addPost);
function isAdmin (req, res, next) {
var token = req.headers['authentication'] || req.body.token || req.params.token;
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({
err: err,
success: false,
message: 'Failed to authenticate token.'
});
} else {
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}

Resources