Any complete example for express-jwt? [closed] - node.js

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 4 years ago.
Improve this question
I want to use express-jwt in my express node application but I can not find any examples which demonstrate signing-in part.
Any help please?

I would recommend that you try to understand the principle of JWT's and how they are passed between server and client and matched server-side against a secret - here's the doc
The payload can be any arbitrary user data - i.E.: just a username or id
Basically you need a service that generates a token on successful authentication (when the user logs in with the proper credentials, i.E.: usr & pwd) and create an additional header with the token to be used in further requests to the server.
For jwt-express you obviously need to install the package (same as with jsonwebtoken) like:
npm install jwt-express --save
then initialize it like:
var jwt = require('jwt-express');
app.use(jwt.init('secret'));
from the docs:
The jwt.init() function returns a middleware function for Express so
it must be called inside app.use(). It will automatically read in the
JWT from either the cookie or the Authorization header (configured by
you) and add a JWT object to the Request object (req). It will also
add the jwt() method to the Response object (res) to create / store
JWTs. jwt.init() must be called before any other jwt method.
These are you options:
cookie: (string) The name of the cookie (default: 'jwt-express')
cookieOptions: (object) Options to use when storing the cookie (default: {httpOnly: true})
cookies: (boolean) If true, will use cookies, otherwise will use the Authorization header (default: true)
refresh: (boolean) Indicates if the JWT should be refreshed and stored every request (default: true)
reqProperty: (string) The property of req to populate (default: 'jwt')
revoke: (function) jwt.revoke() will call this function (default: function(jwt) {})
signOptions: (object) Options to use when signing the JWT (default: {})
stales: (number) Milliseconds when the jwt will go stale (default: 900000 (15 minutes))
verify: (function) Additional verification. Must return a boolean (default: function(jwt) {return true})
verifyOptions: (object) Options to use when verifying the JWT (default: {})
The rest of the logic is up to you to code, but my examples should give you a fair idea how to manage jwt's in your application..
Here is an example how I implemented jwt via jsonwebtoken:
// INFO: Function to create headers, add token, to be used in HTTP requests
createAuthenticationHeaders() {
this.loadToken(); // INFO: Get token so it can be attached to headers
// INFO: Headers configuration options
this.options = new RequestOptions({
headers: new Headers({
'Content-Type': 'application/json', // INFO: Format set to JSON
'authorization': this.authToken // INFO: Attach token
})
});
}
// INFO: Function to get token from client local storage
loadToken() {
this.authToken = localStorage.getItem('token');; // Get token and assign to variable to be used elsewhere
}
and some functionality to store the user-status i.E.:
// INFO: Function to store user's data in client local storage
storeUserData(token, user) {
localStorage.setItem('token', token); // INFO: Set token in local storage
localStorage.setItem('user', JSON.stringify(user)); // INFO: Set user in local
storage as string
this.authToken = token; // INFO: Assign token to be used elsewhere
this.user = user; // INFO: Set user to be used elsewhere
}
and a logout function to destroy the token in the local storage, i.E.:
// INFO: Function for logging out
logout() {
this.authToken = null; // INFO: Set token to null
this.user = null; // INFO: Set user to null
localStorage.clear(); // INFO: Clear local storage
}
In case you use npm's jsonwebtoken, you can set the ttl of the token when generating it:
const token = jwt.sign({ id: idDB }, "secret", { expiresIn: '24h' });
or whatever ttl you desire, the string "secret" refers to the secret that's matched against the server.

Related

How do I call Google Analytics Admin API (for GA4) using an OAuth2 client in node.js?

I've noticed that all the node.js code samples for Google Analytics Admin and Google Analytics Data assume a service account and either a JSON file or a GOOGLE_APPLICATION_CREDENTIALS environment variable.
e.g.
const analyticsAdmin = require('#google-analytics/admin');
async function main() {
// Instantiates a client using default credentials.
// TODO(developer): uncomment and use the following line in order to
// manually set the path to the service account JSON file instead of
// using the value from the GOOGLE_APPLICATION_CREDENTIALS environment
// variable.
// const analyticsAdminClient = new analyticsAdmin.AnalyticsAdminServiceClient(
// {keyFilename: "your_key_json_file_path"});
const analyticsAdminClient = new analyticsAdmin.AnalyticsAdminServiceClient();
const [accounts] = await analyticsAdminClient.listAccounts();
console.log('Accounts:');
accounts.forEach(account => {
console.log(account);
});
}
I am building a service which allows users to use their own account to access their own data, so using a service account is not appropriate.
I initially thought I might be able to use the google-api-node-client -- Auth would be handled by building a URL to redirect and do the oauth dance...
Using google-api-nodejs-client:
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions for Google Analytics scopes
const scopes = [
"https://www.googleapis.com/auth/analytics", // View and manage your Google Analytics data
"https://www.googleapis.com/auth/analytics.readonly", // View your Google Analytics data
];
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes
});
// redirect to `url` in a popup for the oauth dance
After auth, Google redirects to GET /oauthcallback?code={authorizationCode}, so we collect the code and get the token to perform subsequent OAuth2 enabled calls:
// This will provide an object with the access_token and refresh_token.
// Save these somewhere safe so they can be used at a later time.
const {tokens} = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens);
// of course we need to handle the refresh token too
This all works fine, but is it possible to plug the OAuth2 client from the google-api-node-client code into the google-analytics-admin code?
👉 It looks like I need to somehow call analyticsAdmin.AnalyticsAdminServiceClient() with the access token I've already retrieved - but how?
The simple answer here is don't bother with the Node.js libraries for Google Analytics Admin & Google Analytics Data.
Cut out the middleman and build a very simple wrapper yourself which queries the REST APIs directly. Then you will have visibility on the whole of the process, and any errors made will be your own.
Provided you handle the refresh token correctly, this is likely all you need:
const getResponse = async (url, accessToken, options = {}) => {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response;
};
I use Python but the method could be similar. You should create a Credentials object based on the obtained token:
credentials = google.auth.credentials.Credentials(token=YOUR_TOKEN)
Then use it to create the client:
from google.analytics.admin import AnalyticsAdminServiceClient
client = AnalyticsAdminServiceClient(credentials=credentials)
client.list_account_summaries()

Google Sign-In idToken with createSessionCookie causing error - there is no user record corresponding to the provided identifier

Stack:
Google Sign-in (Vanilla JS - client side),
Firebase Functions (ExpressJS)
Client-Side:
My Firebase function express app uses vanilla javascript on the client side. To authenticate I am making use of Firebase's Google SignIn feature client-side javascript web apps, found here.
// Firebase setup
var firebaseConfig = {
apiKey: "AIza...",
authDomain: "....firebaseapp.com",
databaseURL: "https://...-default-rtdb.firebaseio.com",
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
function postIdTokenToSessionLogin(idToken, csrfToken) {
return axios({
url: "/user/sessionLogin", < ----- endpoint code portion found below
method: "POST",
data: {
idToken: idToken,
csrfToken: csrfToken,
},
});
}
// ...
// On sign-in click
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then(async value => {
const idToken = value.credential.idToken;
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
}).then(value => {
window.location.assign("/user/dashboard")
}).catch((error) => {
alert(error.message);
});
Note I am using value.credential.idToken (most sources imply to use this, but haven't found an example saying use this specifically)
Directly after calling signInWithPopup, a new account is created in my Firebase Console Authentication matching the gmail account that was just signed in.
Server-side:
Once I authenticate, I create an axios request passing in the {user}.credential.idToken and following the server-side setup here (ignoring the CSRF - this just doesn't want to work).
In creating the session, I use the following code in my firebase functions express app, the endpoint which is router.post('/sessionLogin', (req, res) => (part of /user route prefix):
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const idToken = req.body.idToken.toString(); // eyJhbGciOiJSUzI1NiIsImt...[936]
admin
.auth()
.createSessionCookie(idToken, {expiresIn}) < ----------- Problem line
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
On the createSessionCookie call, I get the following error & stack trace:
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:149:16)
at Function.FirebaseAuthError.fromServerError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:188:16)
at C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\auth\auth-api-request.js:1570:49
at processTicksAndRejections (internal/process/task_queues.js:93:5)
This is part of the sign-in flow with a existing Gmail account.
What is causing this?
After many hours of searching, Googling - I have seen the light.
For some additional context, this error featured heavily in my struggle "Firebase ID token has invalid signature." - I will get to that in a second.
Further, another issue I also faced was using a local auth emulator for web client-side (javascript), see this for setup.
TL;DR to solve the immediate problem
Client-side remained largely the same, however the documentation provided by Firebase was inaccurate/misleading - thanks to this post, I found the solution. Thus, it follows...
Which is the ID Token? (Client-side):
The examples from here (to allow signInWithPopup), the response (if successful) results in
...
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Looking for an idToken, I found one using result.credential.idToken but no where on the internet on if this was infact the correct token to use.
I ran into this error using the provided idToken above:
Firebase ID token has incorrect "aud" (audience) claim. Expected
"[insert your **projectId**]" but got
"59895519979-2l78aklb7cdqlth0eob751mdm67kt301.apps.googleusercontent.com".
Make sure the ID token comes from the same Firebase project as the
service account used to authenticate this SDK.
Trying other tokens like result.credential.accessToken responded with various verification errors - what to do?
Mention earlier, this solution on Github suggested to use firebase.auth().currentUser.getIdToken() AFTER you have signed in. An example (building on my previous code) is to do the following:
...
.signInWithPopup(provider)
.then((result) => {
// current user is now valid and not null
firebase.auth().currentUser.getIdToken().then(idToken => {
// send this ID token to your server
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
})
})
At this point, you can verify your token and createSessionCookies to your heart's desire.
BUT, a secondary issue I unknowingly created for myself using the Authentication Emulator.
To setup for client-side use:
var auth = firebase.auth();
auth.useEmulator("http://localhost:9099");
To setup for hosting your firebase functions app (assuming you are using this with e.g. nodejs + express, see this for setup, ask in comments, can provide more details if needed)
Using Authentication Emulator caused the following errors AFTER using the above mentioned "fix". Thus, DO NOT RUN the local authentication emulator (with Google sign-in of a valid Google account) as you will consistently get.
Firebase ID token has invalid signature. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token
You can use all your local emulators, but (so far in my experience) you will need to use an online authenticator.

Extend the Expiry Time and Date in Firebase OAuth2 access JWT Token

I want To set the maximum Expiry Date and Time for Firebase OAuth2 JWT Access Token - https://firebase.google.com/docs/database/rest/auth
I tried some methods Not working. Here is the Google's Code to generate an Access Token for Firebase Real-time Database
Google APIs Node.js Client
var {google} = require("googleapis");
// Load the service account key JSON file.
var serviceAccount = require("./myfileauth.json");
// Define the required scopes.
var scopes = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/firebase.database"
];
// Authenticate a JWT client with the service account.
var jwtClient = new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes
);
// Use the JWT client to generate an access token.
jwtClient.authorize(function(error, tokens) {
if (error) {
console.log("Error making request to generate access token:", error);
} else if (tokens.access_token === null) {
console.log("Provided service account does not have permission to generate access tokens");
} else {
var accessToken = tokens.access_token;
console.log(accessToken);
}
});
but it's working for a Short time only I want to Increase its Expiry date and time...
If you want to have longer-lived session tokens, I recommend looking into session cookies. These can be created from the Firebase Admin SDK, and list this as one advantage:
Ability to create session cookies with custom expiration times ranging from 5 minutes to 2 weeks.
It works by taking the ID token (from the client) that is part of the normal Firebase authentication flow, and exchanging that for a session cookie (on the server) with:
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// To only allow session cookie setting on recent sign-in, auth_time in ID token
// can be checked to ensure user was recently signed in before creating a session cookie.
admin.auth().createSessionCookie(idToken, {expiresIn}).then((sessionCookie) => { ...

Access Token vs Refresh Token NodeJS

I'm new to JWT which stands for Json Web Token. I've confused with couple of its terms: Access Token and Refresh Token.
purpose: I wanna implement a user authorization which logs the user out after two hours of being idle (don't request the site or exit from the browser).
To reach that goal I'm trying to follow the below items:
After the user registers/logs-in in the site, I create Access Token and Refresh Token.
Save the refresh token in the DB or cookie.
After 15 minutes the users token the access token expired.
In case of a user being idle for 2 hours, I remove the refresh token from the cookie or DB, else I renew the access token using refresh token.
Is there any optimized way to reach that purpose?
First of all u need to understand the principle of JWT's and how they are passed between server and client and matched server-side against a secret - here's the doc
The payload can be any arbitrary user data - i.E.: just a usrname or id
Basically you need a service that generates a token on successful authentication (when the user logs in with the proper credentials, i.E.: usr & pwd) and create an additional header with the token to be used in further requests to the server.
// INFO: Function to create headers, add token, to be used in HTTP requests
createAuthenticationHeaders() {
this.loadToken(); // INFO: Get token so it can be attached to headers
// INFO: Headers configuration options
this.options = new RequestOptions({
headers: new Headers({
'Content-Type': 'application/json', // INFO: Format set to JSON
'authorization': this.authToken // INFO: Attach token
})
});
}
// INFO: Function to get token from client local storage
loadToken() {
this.authToken = localStorage.getItem('token');; // Get token and asssign to
variable to be used elsewhere
}
and some functionality to store the user-status i.E.:
// INFO: Function to store user's data in client local storage
storeUserData(token, user) {
localStorage.setItem('token', token); // INFO: Set token in local storage
localStorage.setItem('user', JSON.stringify(user)); // INFO: Set user in local
storage as string
this.authToken = token; // INFO: Assign token to be used elsewhere
this.user = user; // INFO: Set user to be used elsewhere
}
and a logout function to destroy the token in the local storage, i.E.:
// INFO: Function for logging out
logout() {
this.authToken = null; // INFO: Set token to null
this.user = null; // INFO: Set user to null
localStorage.clear(); // INFO: Clear local storage
}
In case you use npm's jsonwebtoken, you can set the ttl of the token when generating it:
const token = jwt.sign({ id: idDB }, "secret", { expiresIn: '24h' });
or whatever ttl you desire, the string "secret" refers to the secret that's matched against the server.
btw: If I understand you correctly, your points number 3 and 4 contradict each other..
After 15 minutes the users token the access token expired.
In case of a user being idle for 2 hours, I remove the refresh token from the cookie or DB, else I renew the access token using refresh token.
in case 4 it will be destroyed anyways in 15 mins if you implemented the logic of number 3 correctly

Is there a way to prevent users from editing the local storage session?

I am creating a relational blog where I make use of ember_simple_auth:session to store the session like
{"authenticated":{"authenticator":"authenticator:devise","token":"rh2f9iy7EjJXESAM5koQ","email":"user#example.com","userId":1}}
However, on the developer tools on Chrome (and possibly on other browsers), it is quite easy to edit the email and userId in order to impersonate another user upon page reload.
EDIT #1
From the conversation with Joachim and Nikolaj, I now realized that the best way to tackle this problem is to probe the localStorage authenticity every time I need it (which is only on page reload) instead of attempting to prevent edits.
In order to validate authenticity, I create a promise that must be solved before the AccountSession can be used. The promise serverValidation() requests to create a token model with the current localStorage info, and when the server gets it, it validates the info and responds 200 with a simple user serialization with type as token if the information is legit. You can check more info on the Source Code.
Session Account
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend ({
session: service('session'),
store: service(),
serverValidation: false,
// Create a Promise to handle a server request that validates the current LocalStorage
// If valid, then set SessionAccount User.
loadCurrentUser() {
if (!Ember.isEmpty(this.get('session.data.authenticated.userId'))) {
this.serverValidation().then(() => {
return new RSVP.Promise((resolve, reject) => {
const userId = this.get('session.data.authenticated.userId');
// Get User to Session-Account Block
if(this.get('serverValidation') === true) {
return this.get('store').find('user', userId).then((user) => {
this.set('user', user);
resolve();
}).catch((reason) => {
console.log(reason.errors);
var possible404 = reason.errors.filterBy('status','404');
var possible500 = reason.errors.filterBy('status','500');
if(possible404.length !== 0) {
alert('404 | Sign In Not Found Error');
this.get('session').invalidate();
}
else if(possible500.length !== 0) {
alert('500 | Sign In Server Error');
this.get('session').invalidate();
}
reject();
});
}
else{
alert('Session for Server Validation failed! Logging out!');
this.get('session').invalidate();
resolve();
}
});
});
} else {
// Session is empty...
}
},
serverValidation() {
return new RSVP.Promise((resolve) => {
var tokenAuthentication = this.get('store').createRecord('token', {
id: this.get('session.data.authenticated.userId'),
email: this.get('session.data.authenticated.email'),
authenticity_token: this.get('session.data.authenticated.token'),
});
tokenAuthentication.save().then(() => {
this.set('serverValidation',true);
console.log('Server Validation complete with 200');
resolve();
}).catch((reason) => {
this.set('serverValidation',false);
resolve();
});
});
}
});
Token Controller
# Users Controller: JSON response through Active Model Serializers
class Api::V1::TokensController < ApiController
respond_to :json
def create
if token_by_id == token_by_token
if token_by_email == token_by_id
render json: token_by_id, serializer: TokenSerializer, status: 200
else
render json: {}, status: 404
end
else
render json: {}, status: 404
end
end
private
def token_by_id
User.find(user_params[:id])
end
def token_by_email
User.find_by(email: user_params[:email])
end
def token_by_token
User.find_by(authentication_token: user_params[:authenticity_token])
end
def user_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params.to_unsafe_h)
end
end
There is no way to prevent a user from editing the content of his local storage, session storage, or cookies.
But this should not worry you. The user is identified through the value of the token. The token is generated and sent to him by the authenticator when he logs in. To impersonate another user by editing the session data he would have to know that the other user is logged in, and know the token of that user.
Token is already signed on the server side, a standard JWT mechanism.
Having said that, there can be a couple of ways to check tempering in local storage:
Generate a token the way you already do.
Generate a random secret key to be kept on the server.
Generate a corresponding HMAC using this secret key.
Send the token + HMAC to the user.
When the user sends you this token, first check if HMAC is correct, if not then reject the token right away.
If HMAC is correct, validate the token the way you already do.
Another way:
Along with the token, a HMAC checksum too can be stored separately, and when sent back to the server by the client, check if checksum matches.

Resources