I am trying to understand how token based authentication strategy uses the token (e.g.: JWT) to access the data specific to a user. All I see explained in search results is the part when the user supplies the username and password and then the token is created and verified with each subsequent call.
Le't say I have a Node.JS service with two MongoDB collections: Users and UserMessages. Each entry from UserMessages contains a UserID and a Message.
If an authenticated user wants to see all the messages pertaining to that user, how do I (programatically) know how to filter the correct messages? How do I make the correlation between the token and the user identity (e.g.: UserID or anything else that helps identify the user) that I need for querying the message collection in the next step?
I just wish to understand the concept. I couldn't find anywhere a clear example where the user identity is obtained in code. Example:
apiRoutes.use(function(req, res, next) {
var token = req.body.token || req.param('token')
|| req.headers['x-access-token'];
if (token) {
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message:
'Failed to authenticate token.' });
} else {
req.decoded = decoded;
next();
}
});
}
});
The JWT contains a payload.
Typically, you put the user's ID in this payload. You can put anything you like in there, but since the token is sent on every request you want to keep the payload small. Also, because the JWT is base-64 encoded, anyone can decode the token and see the contents of the payload.
However, because only your server knows the "secret" for the JWT, no one can alter the payload or otherwise create a phony JWT... your server will consider such tokens invalid.
The above code seems to be decoding the payload and storing it on the req object. When it decodes the JWT it is also verifying the signature, so if the decode is successful you can trust the data in the payload. Now you can use the user ID from the payload to get the data you desire.
Related
Hello I have a query regarding JWT and authentication. In authentication, does the server have to store the JWT as well and have it related to the client?
When the client log in, it receives the JWT token and store it in localStorae with the "auth" key:
For example, this is a client's basic code
const user{
email:user_email,
password:user_password
//other field
}
axios
.post("http://localhost:4000/api/login", user)
.then(function (response) {
if (response.data.success === false) {
// password or user does not exist!
} else {
localStorage.setItem("auth", response.data.token);
setTimeout(() => {
history.push("/");
}, 3000);
}
})
.catch(function (error) {
console.log(error);
});
};
Then in our Routes, we can check if there is a token and if so, we can access certain urls that are only accessible when logged in:
const PrivateRoute = (props) => {
const token = localStorage.getItem("auth");
return <>{token ? <Route {...props} /> : <Redirect to="/login" />}</>;
};
But my question comes from: when the client sends a new request (once logged in) doing a POST, GET, DELETE, does the HTTPS packet data need to be in JSON format with the corresponding token? But if so, the server must have the JWT stored to confirm that the user is registered and is who they say they are. So what is the difference with a session?
How do you check if the server doesn't have the JWT stored in its database that the client is who it claims to be?
For example, in django, must there be a field that is jwt?
class User(models.Model):
email = models.EmailField(max_length=254)
token = models.BigIntegerField(default=None)
#Otros campos
A jwt typically contains a set of so called claims, one if them oftentimes is the sub claim which denotes the subject for whom this token was issued. Depending on the particular issuer, this claim may also be named differently. Another typically claim is exp which denotes a timestamp, when this token expires.
Furthermore a JWT should be signed by a trusted party (the issuer). This can be your own server or any other trusted party (for instance sign-in with Google or something like that). If you are able to verify that signature, you are safe to assume the token wasn't tampered with.
So if you find a sub claim in a token whichs signature you can verify, you can easily identify the user. If you furthermore check the expiration, you can reject tokens which are not valid anymore.
But if you don't store the token at the backend, you won't be able to revoke such a token prematurely and it will be valid until it expires. That may, or may be not, a problem, depending on your usecase.
In example project provided by Microsoft here which uses Authorization code flow the acquireTokenByCode method does not return refresh tokens.
From #azure/msal-node here refresh token is not mentioned.
Result returned from the authority's token endpoint.
uniqueId - oid or sub claim from ID token
tenantId - tid claim from ID token
scopes - Scopes that are validated for the respective token
account - An account object representation of the currently signed-in user
idToken - Id token received as part of the response
idTokenClaims - MSAL-relevant ID token claims
accessToken - Access token received as part of the response
fromCache - Boolean denoting whether token came from cache
expiresOn - Javascript Date object representing relative expiration of access token
extExpiresOn - Javascript Date object representing extended relative expiration of access token in case of server outage
state - Value passed in by user in request
familyId - Family ID identifier, usually only used for refresh tokens
please ensure your MSAL authorization code request includes the offline_access scope.
You could use MSAL.js to get token in this case, there is acquireTokenSilent method, it can perform silent renewal of tokens, which means you are no need to get the refresh token by yourself.
Popup
var request = {
scopes: ["Mail.Read"]
};
msalInstance.acquireTokenSilent(request).then(tokenResponse => {
// Do something with the tokenResponse
}).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenPopup(request);
}
}).catch(error => {
handleError(error);
});
Redirect
var request = {
scopes: ["Mail.Read"]
};
msalInstance.acquireTokenSilent(request).then(tokenResponse => {
// Do something with the tokenResponse
}).catch(error => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request)
}
});
It's designed to not return the refresh token if you are using #azure/msal-node.
As they stated in the discussion, the refresh token is handled background, inside the library itself for better security, which I also disagree with.
However, if you insist to have the token, you can manually call the API to the AzureAD endpoint.
I have a bot in NodeJS connected to Google Chat using HTTPs endpoints. I am using express to receive requests. I need to verify that all requests come from Google, and want to do this using the Bearer Token that Google Sends with requests.
My problem is that I am struggling to find a way to verify the tokens.
I have captured the token and tried a GET reuqes to https://oauth2.googleapis.com/tokeninfo?id_token=ey... (where ey... is the token start).
Which returns:
"error": "invalid_token",
"error_description": "Invalid Value"
}
I have tried what Google recommends:
var token = req.headers.authorization.split(/[ ]+/);
client.verifyIdToken({
idToken: token[1],
audience: JSON.parse(process.env.valid_client_ids)
}).then((ticket) => {
gchatHandler.handleGChat(req.body, res);
}).catch(console.error);
And get the following error:
Error: No pem found for envelope: {"alg":"RS256","kid":"d...1","typ":"JWT"}
Any idea where I should head from here?
Edit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com found this, investigating how to use it. The kid matches the one I get.
Worked it out, eventually.
You need to hit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com to get a JSON file containing the keys linked to their KIDs.
Then when a request arrives, use jsonwebtoken (NPM) to decode the token and extract the KID from the header.
Use the KID to find the matching public key in the response from the website above, then use the verify function to make sure the token matches the public key.
You also need to pass the audience and issuer options to verify, to validate that it is your particular service account hitting the bot.
The solution above maybe the correct for Google Chat, but in my experience Google services (e.g. Google Tasks) use OIDC tokens, which can be validated with verifyIdToken function.
Adding my solution here, since your question/answer was the closest thing I was able to find to my problem
So, In case if you need to sign a request from your own code
on client, send requests with OIDC token
import {URL} from 'url';
import {GoogleAuth} from 'google-auth-library';
// will use default auth or GOOGLE_APPLICATION_CREDENTIALS path to SA file
// you must validate email of this identity on the server!
const auth = new GoogleAuth({});
export const request = async ({url, ...options}) => {
const targetAudience = new URL(url as string).origin;
const client = await auth.getIdTokenClient(targetAudience);
return await client.request({...options, url});
};
await request({ url: 'https://my-domain.com/endpoint1', method: 'POST', data: {} })
on the server, validate OIDC (Id token)
const auth = new OAuth2Client();
const audience = 'https://my-domain.com';
// to validate
const token = req.headers.authorization.split(/[ ]+/)[1];
const ticket = await auth.verifyIdToken({idToken: token, audience });
if (ticket.getPayload().email !== SA_EMAIL) {
throw new Error('request was signed with different SA');
}
// all good
Read more about Google OpenID Connect Tokens
I'm using JWT on my application and I define my claims like so:
const claims = {
iss: process.env.DOMAIN,
scope: "game",
email: user.get("email")
};
I verify the token like so:
nJwt.verify(socket.handshake.query.token, app.get("jwt.secret"), (err, decoded) => {
if (err) return;
console.log(decoded.body.email); // doge#doge.com
});
However, it forces me to add session data into claims object. Instead, I want to use the token as a session identifier and keep session values on backend only, such as:
nJwt.verify(socket.handshake.query.token, app.get("jwt.secret"), (err, decoded) => {
if (err) return;
// Since we verified token, read token's session data from backend
});
Is there any example about doing this?
Ps. I use Redis so I can SET/GET values via Redis, but I feel like it's bad practice since I'll be developing whole session thing myself.
To uniquely identify your token, you could take the whole tokenstring as identifier, though this doesn't make much sense since its too long and unhandy.
Instead, jwt has already standardized a claim for this purpose: jti.
So generate your token something like this:
var claims = {
iss: process.env.DOMAIN,
jti: yourGenerateUniqueIdFunction();
};
After JWT verification, you have the jti value.
In the Backend you could model your Session K/V Store like this:
[jti].[keyName] -> [value]
Accessing K/V something like this:
Session.get('3452345.email') //doge#doge.com
Another way how to achieve what you want is just enabling the default session mechanism in express (https://github.com/expressjs/session),
then using https://github.com/tj/connect-redis to store the session in your redis store. In this way you could just easily read and write to the req.session and it will be backed to your configured redis store.
After following the guide for creating a custom identity provider for azure mobile services I can easily generate the appropriate tokens. The code is pretty simple and looks like this:
var userAuth = {
user: { userId : userId },
token: zumoJwt(expiry, aud, userId, masterKey)
}
response.send(200, userAuth);
The definitions for the parameters and code for zumoJwt are located at the link. Azure automatically decodes the token and populates the user on the request object which is what I'd like to simulate.
Basically I'd like to to decrypt the token on the serverside via Node (not .net).
What I ended up doing to validate the token is the following (boiled down). This seems to be about what the azure mobile services is doing on routes that require authorization.
var jws = require('jsw'); // https://github.com/brianloveswords/node-jws
function userAuth() {
var token = ''; // get token as header or whatever
var key = crypto.createHash('sha256').update(global.masterKey + "JWTSig").digest('binary');
if (!jws.verify(token,key)) {
// invalid token logic
} else {
var decode = jws.decode(token)
req.user = {
userId: decode.payload.uid.split(';')[0].split('::')[0]
};
next();
}
}
app.use(authChecker);
The tokens aren't really encrypted - they're just signed. The tokens have the JWT format (line breaks added for clarity):
<header>, base64-encoded
"."
<envelope>, base64-encoded
"."
<signature>, base64-encoded
If you want to decode (not decrypt) the token in node, you can split the value in the . character, take the first two members, base64-decode them (var buffer = new Buffer(part, 'base64')), and convert the buffer to string (buffer.toString('utf-8')).
If you want to validate the token, just follow the same steps you need to re-sign the first two parts of the token (header + '.' + envelope) with the master key, in the same way that the token was created, and compare it with the signature you received on the original token.