Hapi - how to access route scope from request object - hapi.js

I have a Hapi API, using JWT for authentication. My JWT's validate function works as
let validate = (decoded, request, callback) => {
// decoded.permissions is an array of the users's permissions
My route is defined with a dynamic scope as follows;
path: "/{portalId}/somedata",
method: "POST",
config: {
auth: {
strategy: "jwt",
scope: ['user-{params.portalId}']
}
I want to restrict the call to only be allowed is the user's permissions array contains an item for 'user-1', but I don't know what to check against in my validate function.
Where in the request can I find the route's scope restriction for the current call?
Or, how else can I construct this scheme to work for what I need?

Welp, I ended up solving it a different way
My route stayed the same but in the validation function I construct an array of scope string based on the decoded userId and then just add that to the scope
userPermissions: string[] = buildUserPermissions(userId);
// after they pass any checks
return callback(null, true, {scope: userPermissions, user: user} );

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.

How to get ID Token in Firebase Cloud Functions

I need to access user.getIdToken() from firebase cloud functions. I am creating a user like so:
exports.addUser = functions.auth
.user()
.onCreate((user: admin.auth.UserRecord) =>
admin.auth()
.setCustomUserClaims(user.uid, {
"custom_claims": {
"USER": user.email,
"ROLE": 'SUBSCRIBER'
}
})
.then(async () => {
...
// get token
const token = user.getIdToken();
// run fetch
fetch(URL, OPTIONS_WITH_TOKEN);
Obviously user.getIdToken() does not work here, as that is a client method.
createCustomToken does not seem to be the same thing, as it does not contain all the same information (hence it is a custom token), and it adds a claims object instead of inserting the object directly.
How can I get a regular token for a header to run a fetch?
Thanks,
J

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.

ExpressJS apply JWT for file url

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

New to api testing pre-request scripts - Automatically getting access token with OAuth 2.0 Grant Type 'Client Credentials'

I have been stuck for days on this and looked through many articles, but can not find a script that can help me.
The basis of the script is to automatically get authorization token, before i use a POST method.
As said before when getting a access token for this particular api the grant type is Client Crentials and the following fields are needed when manually getting the token :-
Token Name, Grant Type, Access Token URL, Client ID, Client Secrect, Scope and Client Authentication.
Is there a simple script that i can do this for me before actually doing the POST as it tiresome manually getting the token.
Thanks in advance with any help.
Kind Regards
Just an update i have found a way of actually getting the token now , so if you do the following.
Add a new request
Select 'Post'
Enter the api url
Click 'Body'
Click 'x-www-form-urlencoded'
I entered the following 'Keys'(enter your own corresponding 'values') - 'client_id', 'client_secret', 'scope' and 'grant type'
Click 'Send'
This will get you your token, i now need to find a way to either extract the token in a new request or find a way of putting this in the pre-request scripts, so I am able to enter the data need as 'raw' JSON.
Again if anyone can help, would appreciate it.
Kind Regards
Would this be any help to you? Or at least get you closer to what you need?
If you add this script to the Collection level pre-request script it will get the token and set this as the jwt variable. You can use this variable in the Headers for the main requests, using the {{jwt}} syntax - This script also gets the expiry_in value from the token response and sets this as a variable.
On each request in the collection, it will run the script and check to see if you have the AccessTokenExpiry and jwt properties in the environment file, it also checks to see if the token has expired. If any of those statements are true, it will get another token for you. If those are ok, it will use what you have set.
const moment = require('moment')
const getJWT = {
url: `<your token base path>/Auth/connect/token`,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
mode: 'urlencoded',
urlencoded: [
{key: 'grant_type', value: 'client_credentials'},
{key: 'scope', value: '<scope details>'}
{key: 'client_id', value: 'your creds'}
{key: 'client_secret', value: 'your creds'}
]
}
}
var getToken = true
if (!_.has(pm.environment.toObject(), 'AccessTokenExpiry')
|| !_.has(pm.environment.toObject(), 'jwt')
|| pm.environment.get('AccessTokenExpiry') <= moment().valueOf()) {
} else {
getToken = false
}
if (getToken) {
pm.sendRequest(getJWT, (err, res) => {
if (err === null) {
pm.environment.set('jwt', `Bearer ${res.json().access_token}`)
var expiryDate = moment().add(res.json().expires_in, 's').valueOf()
pm.environment.set('AccessTokenExpiry', expiryDate)
}
})
}
To access the Collection level elements, if you hover over the collection name and click the ... icon, this will display a list of menu options. Select edit.