ExpressJS apply JWT for file url - express

So I'm trying to make authorization for routes with JWT, it all worked if used on routes.
app.get('/user/list', jwtMiddleware, action);
And the jwtMiddleware content is (more or less):
var token = req.headers.authorization;
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, process.env.SECRET_TOKEN, function(err, decoded) {
if (err) {
return res.status(401).send({
success: false,
message: 'Sign in to continue.'
});
} else {
// if everything is good, save to request for use in other routes
next();
}
});
} else {
// if there is no token
// return an error
return res.status(401).send({
success: false,
message: 'Sign in to continue.'
});
}
it works, but I have these image files in uploads/ folder which accessible by /upload/image-1.jpg and I want to prevent direct access to /upload/image-1.jpg by using wildcard routes app.get('/upload*', jwtMiddleware, action);
then I try accessing random route with upload prefix like /upload/test, the jwt middleware works. But if I explicitly type /upload/image-1.jpg the browser just show the image, it's like the middleware or wildcard route (/upload*) is not accessed (the console.log inside middleware didn't even fired).
Previously I use restify and restify-jwt-middleware, it could handle this case flawlessly but in express I can't find out why it doesn't work. Maybe because restify-jwt-middleware automatically registers all routes into jwt validation whereas express need to declare each route with jwt middleware manually.
is there anything I miss in this case? thank you.

add/modify to another route like app.get('/upload/:image', jwtMiddleware, action)
this will check all the route you mentioned /upload/*
EDIT :
put the static files(eg.uploaded files somewhere like images/upload) and route them using the serveStaticFiles plugin restify and put jwt middleware to verify the user login status.
server.get(
'/uploads/*',
jwtMiddleware,
restify.plugins.serveStaticFiles('./images/upload')
);

In case anyone still confused, here's my answer in express which is similar approach to yathomasi's
// the fake route
app.get('uploads/:name', jwtMiddleware, (req, res, next) => {
if (fs.existsSync('./realpath/' + req.params.name)) {
res.sendFile('./realpath/' + req.params.name);
} else {
res.status(404).body({status : 'ERROR', message : 'File not found'});
}
});
this way, the uploads/somefile.jpg is treated as route url not file url and will be processed by jwtMiddleware

Related

PassportJS OAuth2Strategy: authenticate returns 400 instead of redirecting

I'm trying to setup discord oauth2 pkce using passportjs and the passport-oauth2
const discordStrategy = new OAuth2Strategy({
authorizationURL: 'https://discord.com/api/oauth2/authorize',
tokenURL: 'https://discord.com/api/oauth2/token',
clientID: DISCORD_CLIENT_ID,
clientSecret: DISCORD_CLIENT_SECRET,
callbackURL: DISCORD_CALLBACK_URL,
state: true,
pkce: true,
scope: ['identity', 'scope'],
passReqToCallback: true,
},
(req: Request, accessToken: string, refreshToken: string, profile: DiscordUserProfile, cb: any) => {
prisma.user.findUnique({ where: { email: profile.email ?? '' }}).then(foundUser => {
if (foundUser === null) {
// Create a new user with oauth identity.
} else {
cb(null, foundUser)
}
}).catch(error => {
cb(error, null);
})
});
I've been following the google example as well as some others, these examples indicate that, I should be able to use:
passport.use('discord', discordStrategy);
and
authRouter.get('/discord', passport.authenticate('discord'));
and this should redirect to the OAuth2 providers login page, but instead, I get a 400 Bad Request "The request cannot be fulfilled due to bad syntax." The response body contains an object:
{"scope": ["0"]}
Why is this happening instead of the expected redirect?
My intention is that, once the user logs in, I should get a code, then I can post that code and the code verifier to get an access token, then once the access token is obtained, the actual authenticate call can be made
Edit: I put breakpoints in the passport.authenticate function and I stepped through it. It does actually get through everything and it calls the redirect. The parsed URL it generates, even if I copy it and manually navigate to the URL, it gives me the same, just gives:
{"scope": ["0"]}
and no login page, why?
If you add a version number to the base api url, e.g. /v9 it gives a full error message.
It turned out I had typo'd the scopes, I had 'identity' instead of 'identify' - now this part of the process is working as expected.

Checking the validity of JWT Tokens - beforeEnter

I've got a function that runs 'beforeEnter' in the Vue router to verify that the user has been authenticated, otherwise triggers a message.
It checks to see if a (jwt) token is saved in the localStorage - this works if the user signs out manually, as it removes the token from the localStorage. However when the token expires it still remains in the localStorage so the function thinks ((localStorage.token)) the user is logged in.
The server still blocks any requests made as the token is invalid - so is safe.
How do I check the token's validity on the server side, in the 'beforeEnter' middleware, before the page loads?
Do I need to make an endpoint that checks a tokens validity and returns the result? (I'm using fetch(), however I've seen people use axios interceptors...)
Worth nothing that I'm not using VUEX, and there seems to be more details on that?
function protectedPage(to, from, next) {
if (localStorage.token) {
next();
} else {
Vue.toasted.show("The session has ended. Please login.", {
theme: "toasted-primary",
position: "top-center",
duration: null,
action: {
text: "Login",
onClick: (e, toastObject) => {
next("/");
toastObject.goAway(0);
}
}
});
next("/");
}
}
Since exp is part of the payload, and JWT is just a base64 string, you can just decode it and check the exp time on your Vue app.
This is a function to decode JWT token and get the payload (taken from here)
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(Buffer.from(base64, "base64").toString("ascii").split("").map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
};
and check it on your beforeRouteEnter function:
beforeRouteEnter (to, from, next) {
if (localStorage.token) {
const jwtPayload = parseJwt(localStorage.token);
if (jwtPayload.exp < Date.now()/1000) {
// token expired
deleteTokenFromLocalStorage();
next("/");
}
next();
} else {
next("/");
}
},
You don't really need to check it on your backend server, since there's no security concern by decoding the JWT token payload and checking it in on the client side. Plus it saves you one HTTP request every time a user access a route.
You need a backend middleware which bound to each API call and validates user session if still exists and has same tokens.
If the session has been expired or token has been changed and doesn't match with the current user session, you can redirect user to the login page from backend and force him to create a fresh session.
I think you don't need to fetch the authentication for each route entrance, just block the backend api calls and return a message or redirect to the login page. User can still browse the pages with the expired session info but won't be able to perform any fetch or form actions.

How to send Oauth token from callback at the server to the client?

I am using expressJs and passport for authentication. I am using Google Oauth2.0 for login with standard Passport GoogleStrategy. At client I am using axios for sending a login request to the server. My login routes are :
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] }));
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/"}),
function(req, res) {
const token = jwt.sign({id: req.user.id}, configAuth.secretKey);
console.log("generated token: ", token);
res.json({success: true, token: 'bearer ' + token});
}
);
I am using the user information from the callback to generate the JWT which I want to sent the client.
At the client I am using axios to send request and get the JWT and store it in localstore.
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})
.catch((error) => {
// if(error.response.status === 401) {
// this.setState({ message: 'Login failed. Username or password not match' });
// }
console.log("Login error", error);
});
But Axios doesn't wait for the redirect to happen and returns a HTML document with Loading... message. If you try to access the API in the browser, it returns the desired JSON object. Is there a way to wait for redirects. Should I use another library to send login request?
I tried sending the token as url parameter with
res.redirect()
but client and server are at different ports so it doesn't work.
Is there another way to do it?
Google's OAuth2 pathway redirects your browser, resulting in page reloads, a couple of times before it completes. As a result, your client-side code,
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})...
will never reach the .then() block. You probably see this in the browser; you click a button or something to navigate to 'http://localhost:3001/google', and your localhost:3001 server re-directs your browser to a Google login page. Now that your browser is at the login page, it has no memory of the axios.get statement above--that webpage code is gone.
You need to handle the JWT in client-side code that your server sends in response to
axios.get('http://localhost:3001/google/callback').
This is your browser's final stop in the OAuth2 path--once you get there, you won't be re-directed again. You can put your axios.get function above inside that client-side code.
If you haven't solved the problem, there is a workaround use 'googleTokenStategy' instead of googleOAuth on passportjs. That way you can use react's GoogleLogin plugin to receive the access token from the front end and send it by axios.post to the backend link then set up the jwt. Reference here

Using node-spotify-web-api to grant user access and fetch data

So I'm new to using OAuth and I honestly got quite lost trying to make this work. I looked up the documentation for Spotify's Authorization code and also found a wrapper for node which I used.
I want to be able to log in a user through spotify and from there do API calls to the Spotify API.
Looking through an example, I ended up with this code for the /callback route which is hit after the user is granted access and Spotify Accounts services redirects you there:
app.get('/callback', (req, res) => {
const { code, state } = req.query;
const storedState = req.cookies ? req.cookies[STATE_KEY] : null;
if (state === null || state !== storedState) {
res.redirect('/#/error/state mismatch');
} else {
res.clearCookie(STATE_KEY);
spotifyApi.authorizationCodeGrant(code).then(data => {
const { expires_in, access_token, refresh_token } = data.body;
// Set the access token on the API object to use it in later calls
spotifyApi.setAccessToken(access_token);
spotifyApi.setRefreshToken(refresh_token);
// use the access token to access the Spotify Web API
spotifyApi.getMe().then(({ body }) => {
console.log(body);
});
res.redirect(`/#/user/${access_token}/${refresh_token}`);
}).catch(err => {
res.redirect('/#/error/invalid token');
});
}
});
So above, at the end of the request the token is passed to the browser to make requests from there: res.redirect('/#/user/${access_token}/${refresh_token}');
What if insted of redirecting there, I want to redirect a user to a form where he can search for artists. Do I need so somehow pass the token around the params at all time? How would I redirect a user there? I tried simply rendering a new page and passing params there but it didn't work.
you could store the tokens in a variety of places, including the query parameters or cookies - but I'd recommend using localstorage. When your frontend loads the /#/user/${access_token}/${refresh_token} route, you could grab the values and store them in localstorage (e.g. localstorage.set('accessToken', accessToken)) and retrieve them later when you need to make calls to the API.

Angular 2 AuthHttp with jwt not connecting

I'm trying to use jwt's authHttp to set an API connection to a particular Back End. I'm trying to make it first without any token so I can test it but it seams like it's not even getting connected. I'm using it as following:
this.authHttp.get('localhost:3001/api/basic')
.subscribe(
data => console.log("data"),
err => console.log(err),
() => console.log('Request Complete')
);
The error I'm getting in the console is AuthHttpError {}
I've set my ngModules as it say in the guide:
providers: [
{
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
}
And
function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig({noTokenScheme : true}), http);
}
The thing that drive's me crazy is that using http it works fine like this:
this.http.get('http://localhost:3001/api/basic').subscribe(
data=> console.log(data),
error=> console.log("Getting Error")
);
You are probably thinking "Why he is not using http then instead of authHttp?". Well, that's because setting a heather "Authorization" and its token seams impossible with http.
Any help or guidance would be extremely helpful.
If you don't need JsonWebTokens but simply want to add custom headers, you can do it this way without having to import the angular2-jwt library :
In your service :
private customHeaders: Headers = this.setCredentialsHeader();
setCredentialsHeader() {
let headers = new Headers();
let credentials = window.localStorage.getItem('credentials2');
headers.append('Authorization', 'Basic ' + credentials);
return headers;
}
someMethod() {
let url = 'your.URL.to.API';
return this.http
.get(url, { headers: this.customHeaders })
.map(result => {
console.log(result);
});
}
This way you can add your Authorization header with the type of data you want.
If it's a Authorization Bearer type header you are looking for and use it with angular2-jwt, you can use the default configuration first before trying to provide your own AuthHttp instance through the factory. It will be much simpler to debug and figure where the problem is.
From the documentation : https://github.com/auth0/angular2-jwt#configuration-options
AUTH_PROVIDERS gives a default configuration setup:
In your module with your service, just import the AUTH_PROVIDERS like this :
import { AUTH_PROVIDERS } from 'angular2-jwt';
...
#NgModule({
...
providers: [
AUTH_PROVIDERS,
...
]
})
and simply use the AuthHttp instance in your service like you did.
You should see in the Navigator Network tab your headers being added to your request.
EDIT :
As stated in the documentation, it is appending the token value in the headers from the Token Getter Function defined in the AUTH_PROVIDERS by default.
You therefore need to add your JWT in your LocalStorage with the default name id_token.
To give you my working example, I'm setting a JWT upon the authentication process, where I get a JWT as a response from my Http Call :
auth.service.ts
this.identityService.setToken(token.accessToken);
identity.service.ts
setToken(token?) {
if (token) {
window.localStorage.setItem('id_token', token);
} else {
window.localStorage.removeItem('id_token');
}
}
You should be able to see your JWT in your network tab if done correctly.
Afterwards, the AuthHttp instance should add the headers to your requests as intended...
It might not work correctly if your Token is not a JWT. To check if it's a good one, you can use a website such as https://jwt.io/ where it will be decoded.
If it's still not working, this means the problem is coming from elsewhere. A service not provided correctly, etc.