I've searched around a bit, but have not found any clear, up-to-date, answers on this topic.
I'm trying to implement JWT authentication in my NextJS application. The following is what I have so far.
/login endpoint that will (1) check that the user/pass exists and is valid, and (2) create a JWT token based on a private RS256 key.
Created a middleware layer to verify the JWT
The creation of the JWT is fine - it works perfectly well reading the key from the file-system and signing a JWT.
However, I've run into the problem of the middleware being unable to use node modules (fs and path) because of the edge runtime (read here). This makes it so I'm unable to read the public key from the FS.
What is the proper way to verify a JWT token on every request? I've read that fetching from middleware is bad practice and should be avoided. All other reference on this topic (that I found) either uses a "secret" instead of a key (and can therefor be put into process.env and used in middleware) or glosses over the fact (1). Or should I just create a separate express application to handle JWT creation/verifying?
What I do is add a file (_middleware.tsx) within pages. This ensures the file runs each page load but is not treated as a standard page. In this file lives an Edge function. If the user is not signed in (no JWT in cookies) and tries to access a protected page, he is immediately redirected to /signin before the server is even hit.
import { NextResponse } from "next/server";
const signedInPages = ["/admin", "/members "];
export default function middleware(req) {
if (signedInPages.find((p) => p === req.nextUrl.pathname)) {
const { MY_TOKEN: token } = req.cookies;
if (!token) {
return NextResponse.redirect("/signin");
}
}
}
If you signed the token from
import jwt from "jsonwebtoken";
then you can use same jwt for verifying token inside a middleware function. you create _middleware.js inside pages directory and middleware will run for each request before request hits the endpoint:
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export async function middleware(req, ev) {
const token = req ? req.cookies?.token : null;
let userId=null;
if (token) {
// this is how we sign= jwt.sign(object,secretKey)
// now use the same secretKey to decode the token
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
userId = decodedToken?.issuer;
}
const { pathname } = req.nextUrl;
// if user sends request to "/api/login", it has no token. so let the request pass
if (
pathname.includes("/api/login") || userId
) {
return NextResponse.next();
}
if (!token && pathname !== "/login") {
return NextResponse.redirect("/login");
}
}
you send the token in the post request header.
{Authorization: 'Bearer ' + token}
And then verify that token on the server, before sending data back
To verify the token you create a middleware function, then use that function on any route you want to portect
router.get("/", middlewarefunction, function (req: any, res: any, next: any) {
res.send("/api")
})
Related
For example, when I log in I create an access token with payload { 'userId': 1, '2fa': false} (/login route), then I do another route '/login/auth' that checks for example if a one time password is correct, if it is I created another jwt but this time with 2fa set to true. Then proceeding routes will check whether 2fa is true to run. If not, it will error. Is this a proper way to do this or hacky?
// middleware
const auth = (req, res, next) => {
const token = req.header('access-token');
if (!token) return res.status(401).json('Not Authorized');
try {
const payload = jwt.verify(token, 'secret1');
req.payload = payload
next();
} catch (ex) {
res.status(400).json('Invalid.');
}
};
// routes
router.post(
'/login',
async (req, res) => {
try {
/* login database stuff goes here if successful creates access token
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: false },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/login/auth2',
auth,
async (req, res) => {
try {
/* verifying two factor auth logic goes here
* if succesful approves the 2fa
*/
const token = jwt.sign(
{ userId, twoFactorAuthenticated: true },
'secret1',
);
res.status(200).json(token);
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
router.post(
'/some-route',
auth,
async (req, res) => {
try {
if(!req.payload.twoFactorAuthenticated)
return res.status(400).json('user has not completed second factor auth')
} catch (e) {
console.log(e);
return res.status(500).json(e);
}
},
);
My use case doesn't really require login authorization but for something different but same concept applies.
The process you are suggesting will work, but it can be improved.
Set JWT claims
Since the payload of JWT is not encrypted, it is only meant for storing non sensitive that is usefull in the verifing process. Although the payload is not encrypted, it is signed and cannot be changed without knowing the secret or private key.
There are a number of standard claims which might be useful, besides your own custom twoFactorAuthenticated claim. You always should set an expiration time (exp) on your tokens. For the userId you may use the subject claim (sub).
Use middleware for authentication of routes
Once the two factor authentication is done, you want to check the JWT on all protected routes. Now, you have put the check in the ‘some-route’ controller itself, but it is preferable to define middleware for this if you have more then one protected route.
Use a http only cookie to store JWT with a short life time
In your solution you send the token to your client and let the client add it to the authentication header. This probably means that the token will be stored in local storage. It may be more secure to use a secure http cookie for this, with a short life time, like 10 minutes or so. This means that if the token is compromised, your API is vulnerable for a maximum of 10 minutes. For a user, having to authenticate every 10 minutes is a horrible UX. So that’s why you may want to implement refresh tokens.
Use refresh token and use it only once
In your example, after the twofactor authentication is verified, you send a token back with the twoFactorAuthenticated set to true. As suggested before, you can send this in a secure cookie with a short life time instead. At the same time, you generate a refresh token with a much longer expire time, e.g. 4 hours. When the cookie expires after 10 minutes, the client can use the refresh token once to get a new cookie and a new refresh token.
Important:
The refresh token can only be used to get a new cookie, not on other routes. It can be stored in local storage and send in the authentication header as a bearer token.
The refresh token may only be used once, so it needs to have a unique id and you need to store the id on the server side. If a refresh token is used you set it to invalid.
If a refresh token is used, you check if it is not used before. If it is used for the second time, something strange is happening, because with a new cookie, the user also gets a new refresh token. At that moment you just set all valid refresh tokens of the user to invalid. Than yo are sure they have to log in again on all devices, after their cookie expires.
This requires some implementation on client and server side, but I think it makes your authentication process much more solid.
You can use '2fa' boolean value in payload, it does not show any secret information. It is impossible to create this token without the JWT secret key which is private to you. But you can do this with only 'userId' as well, store '2fa' value in user table and check the value when you verify the token.
hey guys. i'm working with axios,nodeJS and vue . i want
to generate an accessToken in node (it's done) and get it
with axios (it's done too) and cache it to browser cache with
vue(don't know how), so whenever i want to send a request to
node,i could send it with headers of axios , i dont know if it's
possible or not ? need some help around here.
Yes, that's possible and is pretty common.
To do it, you just have to store the token in the browser memory (usually in the localStorage) and then use axios interceptors on the requests to automatically add the token.
How you can add items to local storage?
localStorage.setItem('my-key',my-value)
you can find the complete docs here.
axios.interceptors.request.use(
config => {
//here retrieve the token and add it (if present)
//most probably you would use VUEX for storing it
//in you app
const token = yourLocalStorageAccessor.getAccessToken();
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
// config.headers['Content-Type'] = 'application/json';
return config;
},
error => {
Promise.reject(error)
});
You can read about axios interceptors here.
I would like to implement auto refresh jwt token before every request to GraphQL with Apollo middleware in React Native app. After every user login he gets two tokens: access and refresh. Access token it is the short one for 30-60 min for using in authorization header. And refresh token it is the long one for 60 days for confirm of refresh token graphql mutation.
My flow:
User login and gets 2 tokens -> put access token to authorization header with Appollo setContext.
User make request to GraphQL -> check expireTime of accessToken on a client side:
-> if it is not expired -> confirm request
-> if it is has expired -> call GraphQL refreshToken mutation -> get new tokens -> confirm request.
For keeping tokens on the client side i use KeyChain storage. Can you tell me please should i use Apollo cache for keeping tokens too? Should i write Apollo state for tokens? And how i can implement my flow?
GraphQL mutation
mutation UpdateTokens($refreshToken: String!, $refreshTokenId: String!)
{
updateTokens(refreshToken: $refreshToken, refreshTokenId: $refreshTokenId) {
user {
name
phone
}
accessToken
refreshToken
}
}
App.js
import React from 'react'
import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link'
import { ApolloProvider } from 'react-apollo'
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
import * as Keychain from 'react-native-keychain'
import AppNavigator from './AppNavigator'
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const cache = new InMemoryCache()
const authLink = setContext(async (req, { headers, ...context }) => {
const tokens = await Keychain.getGenericPassword()
const accessToken = tokens.username
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : ''
},
...context
}
})
const client = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache,
connectToDevTools: true
})
const App = () => {
return (
<ApolloProvider client={client}>
<ApolloHooksProvider client={client}>
<AppNavigator />
</ApolloHooksProvider>
</ApolloProvider>
)
}
export default App
I honestly think you are on the right path -- as I read your required functionality I thought of apollo-link-context and was happy to see you were taking that approach. We recently had to implement similar functionality in a react-native app, which entailed attaching custom headers with authentication-related data. To retrieve this data we were required to make an async request, although ours was over the network to a third-party service. We did this all in setContext from the client just as you are. This worked well.
I do not think you need to concern yourself with updating your Apollo cache with your tokens, manually or otherwise, for at least a few reasons. First, given the quasi-sensitive nature of what you are storing, it would be best practice to defer to the more secure storage solution, which in this case is likely the Keychain. In addition, having a single source of truth for the tokens in your application can keep things clean.
Assuming you do not want to write this data to cache, I would double-check Apollo client is not automatically doing so. For example, if you for some reason previously queried your token data, Apollo might automatically update your cache upon receiving the mutation payload. Just something to be mindful of.
I am currently working on a authentication service for a node.js microservices application using typescript, WebStorm, passport and jwt. While trying to add the route to "/api/login", I am noticing that the intellisense does not seem to pick up the user object of req.user or the authorization object of req.header.authorization. For example, the following method is not working because it can not find the user object:
private generateToken(req: Request, res: Response, next: NextFunction){
req.token = jwt.sign({
id: req.user.id,
firstname: req.user.firstname,
lastname: req.user.lastname,
roles: req.user.roles
}, process.env.AUTH_KEY, {
expiresIn: "7d"
});
return next();
}
I am using the Request object from express:
import { NextFunction, Request, Response, Router } from "express";
Would I need to use a different Request object?
Also, if I need to force authentication to certain api routes but lock other routes down, how should this be done using passport-jwt? I know there is an express-unless package that I can use for express-jwt.
Not sure why this question downvoted, maybe because it should be two separate questions.
You can extend type declarations for Express.
Extending the express type definitions
Add a file library-ext.d.ts into your source directory with this.
declare module 'express' {
export interface Request {
user?: any;
}
}
For req.header.authorization try: req.headers['authorization']. Notice the 's'.
Relating to general authentication
It depends whether your registered users can also use the guest routes. If you never need identity on the guest routes then just register the passport authentication middleware only on the authenticated routes, or split the routes into separate routers. That's fairly simple to do, just search stack overflow or look in the docs for it.
The more complicated case is when you need both authenticated and non-authenticated users to access a route - think either a guest or authenticated customer adding something to a cart. Unfortunately passport-jwt rejects with a 401 when a token is not in the authorzation header, so the easiest way I found, rather than forking the project or rolling my own strategy, was to use middleware to add a known value to represent an otherwise anonymous request. Then just make sure that middleware is before the passport authentication in the affected routes. Here's a snippet to get you going:
CoreCtrl
class CoreCtrl {
simulateAnonymous(req, res, next) {
if (!req.headers.authorization) {
req.headers.authorization = 'Bearer guest-token';
}
return next();
}
}
Then somewhere in your Express Setup
setupRouters() {
// the public and admin routers are bound to the application
const coreCtrl = new CoreCtrl(this.serverOpts);
const anonymousCtrl = coreCtrl.simulateAnonymous.bind(coreCtrl);
this.routers.admin.use(anonymousCtrl);
this.routers.admin.use(passport.authenticate('UserBearer', { session: false }));
this.routers.public.use(anonymousCtrl);
this.routers.public.use(passport.authenticate('CustomerBearer', { session: false }));
}
Note that I had separate routers for public and admin set up here, that's not necessary but just to illustrate how to do it.
Then in the bearer strategy, you would have some code similar to this.
/**
* Run the strategy
*
* #param token {String} The JWT Token
* #param done {Callback} Callback function
*/
exec(token:string, done):Promise<any> {
// this is the workaround to support not passing a token for guest users.
if (token === 'guest-token') {
return done(null, {
userId: 'guest',
roles: ['guest']
});
}
// otherwise decode the token and find the user.
}
Finally, in some later Middleware you can check if the 'guest' role has access to the protected resource. I'd recommend acl module to manage role-based ACL list.
Property 'isAuthenticated' does not exist on type 'Request'.
Google lands here for that search, and the solution is to use Request/Response types in Express. My code was defaulting to the Fetch API's Request/Response definitions.
import { Express, NextFunction, Request, Response } from 'express'
The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet