Is it safe for my lambda function to trust the accessToken passed by the user and checked by the lambda authorizer to perform CRUD db operations?
For example:
const authToken = event.headers['Authorization'];
if (!authToken) throw new Error('No auth token found so no username');
var decodedToken = jwt_decode(authToken);
const userName = decodedToken.username; //---- BUT CAN WE TRUST THIS? ----
let params = {
TableName: "myTable",
IndexName: 'userName-gsi',
KeyConditionExpression: 'userName = :userName',
ExpressionAttributeValues: {
':userName': userName,
},
Limit: 1,
};
let data = await dynamodb.query(params).promise();
return {
statusCode: 200,
headers: utils.getResponseHeaderApplicantifyCors(),
body: JSON.stringify(data.Items[0]),
};
In your example you're only decoding the JWT, so the only verification made is that the token is in JWT form. That is not enough to guarantee, that the JWT is originating from trusted party.
The minimum you should do, is to also verify the contents of the JWT token. Steps for that are listed here: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
Related
I have two simple authentication callback functions "jwt" and "session" which check if the user object exists and create the session if so.
callbacks: {
jwt: async ({ token, user }) => {
if(user) {
token.id = user.id
}
return token
},
session: ({ session, token }) => {
if(token) {
session.id = token.id
}
return session
},
}
My issue is, and I have been searching a lot to find information concerning this, why isn't this jwt automatically saved to cookies?
I find that my session is created and I am successfully "logged in", however if I look into my local storage there are no jwt cookies saved.
Do I have to manually save the jwt to my cookies in the jwt callback? Or is the jwt cookie not even required in the case of jwt session strategy? I need jwt cookies because from what I've read about middleware most solutions use cookies and decrypt the jwt to see if the user is logged in, instead of checking the getSession() hook.
You might need to to explain your problem in more detail since I can´t really tell what you already implemented and left out for simplicity sake. I hope this helps you anyway:
The steps to add the cookie look roughly like this:
Create / sign a jwt with a npm package like jose or jsonwebtoken
Set the header of your response and add your signed jwt to it; return it to the client
import { SignJWT, jwtVerify, JWTVerifyResult } from "jose";
async function setCookie(response, user: {id: number}) {
const token = await generateJwtToken(user);
response.setHeader("Set-Cookie", [
`user=${token};` +
"expires=" + new Date(new Date().getTime() + 1 * 86409000).toUTCString() + ";"
]);
response.status(200).json({message: "Successfully set cookie"})
}
async function generateJwtToken(user: { id: number }) {
return await new SignJWT({id: user.id})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("24h")
.sign(new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR));
}
Verify the jwt on further requests with the same package as in 1.
export async function verifyJwt(request) {
const token = request.cookies["yourCustomUser"];
const verified: JWTVerifyResult = await jwtVerify(
token,
new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR),
);
verified.payload.status = 200;
return verified.payload;
}
In addition to that you might wanna add sameSite=Strict, secure or path=/. For further information you should have a look at developers.mozilla
Also make sure to add error handling for expired Jwts etc.
According to my understanding a JWT can be broken down into a Header, Payload and a Signature. The signature is created using Header+Payload+Secret. The secret is stored by the server and is never shared.
When a client sends a JWT to the server. The server can authorize it by accessing the Header , Payload received from JWT and combine it with the secret to create a Test Signature. Then the server can check if Test Signature = Signature received from the client JWT and make sure the data is not altered.
• Does this mean that the entire JWT authorization is dependent on the Secret and the whole authorization process is compromised if it is leaked?
Secret is matter to verify JWT
This diagram is normal sequence to use JWT for accessing resource by API call.
I would like to demo with real code.
Using jsonwebtoken java-script library on node-js
1. JWT generate step.
function name: generate_token()
first function parameter: secret
second function parameter: user name (or payload)
const jwt = require("jsonwebtoken")
const jwtExpirySeconds = 600
const generate_token = (jwtKey, username) => {
const token = jwt.sign({ username }, jwtKey, {
algorithm: "HS256",
expiresIn: jwtExpirySeconds,
})
console.log("JWT token:", token, '\n')
const [header, payload, signature] = token.split(".")
console.log("1) header:", header)
console.log(JSON.parse(Buffer.from(header, "base64").toString("utf8")), '\n')
console.log("2) payload:", payload)
console.log(JSON.parse(Buffer.from(payload, "base64").toString("utf8")), '\n')
console.log("3) signature:", signature)
}
const my_args = process.argv.slice(2);
generate_token(my_args[0], my_args[1]);
generate token with secret and user name
$ node generate_token.js my-secret user1
JWT token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwiaWF0IjoxNjU5NDA3NzQ0LCJleHAiOjE2NTk0MDgzNDR9.MMIxotVJjMBLFOg0nusePz4L62n7VZ4Q-fW_u392qDQ
1) header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{ alg: 'HS256', typ: 'JWT' }
2) payload: eyJ1c2VybmFtZSI6InVzZXIxIiwiaWF0IjoxNjU5NDA3NzQ0LCJleHAiOjE2NTk0MDgzNDR9
{ username: 'user1', iat: 1659407744, exp: 1659408344 }
3) signature: MMIxotVJjMBLFOg0nusePz4L62n7VZ4Q-fW_u392qDQ
2. Verify JWT step.
function name: verify_token()
first function parameter: secret
second function parameter: JWT token
const jwt = require("jsonwebtoken")
const verify_token = (jwtKey, token) => {
try {
payload = jwt.verify(token, jwtKey)
console.log("JWT token verify:", payload, '\n')
} catch (e) {
if (e instanceof jwt.JsonWebTokenError) {
console.log("JWT token error: 401")
return
}
// otherwise, return a bad request error
console.log("JWT token error: 400")
}
}
const my_args = process.argv.slice(2);
verify_token(my_args[0], my_args[1]);
2.1 verify token with correct secret and JWT token
$ node verify_token.js my-secret eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwiaWF0IjoxNjU5NDA3NzQ0LCJleHAiOjE2NTk0MDgzNDR9.MMIxotVJjMBLFOg0nusePz4L62n7VZ4Q-fW_u392qDQ
JWT token verify: { username: 'user1', iat: 1659407744, exp: 1659408344 }
2.2 if verify token with wrong secret and same JWT token
$node verify_token.js wrong-secret eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwiaWF0IjoxNjU5NDA3NzQ0LCJleHAiOjE2NTk0MDgzNDR9.MMIxotVJjMBLFOg0nusePz4L62n7VZ4Q-fW_u392qDQ
JWT token error: 401
I have a small ExpressJS server with a login feature. Some pages are secured with self-written authenticate middleware that checks if your Json WebToken is correct.
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
const jwt = require('jsonwebtoken');
// Simple array holding the issued refreshTokens
const { refreshTokens } = require('../lib/auth.js');
module.exports.authenticateToken = function(req, res, next) {
const token = req.cookies.token;
const refreshToken = req.cookies.refreshToken;
if(token === null) return res.redirect('/login');
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if(verified) return next();
} catch(err) {
try {
const refreshInDb = refreshTokens.find(token => token === refreshToken);
const refreshVerified = refreshInDb && jwt.verify(refreshToken, process.env.REFRESHTOKEN_SECRET);
const newToken = jwt.sign({ email: refreshVerified.email }, process.env.TOKEN_SECRET, { expiresIn: 20 });
res.cookie('token', newToken, { maxAge: 900000, httpOnly: true });
return next();
} catch(err) {
return res.redirect('/login');
}
}
};
Now is this code correct & secure enough for a small webapplication? Am I missing stuff? It feels so... easy?
Seems like you are veryfing both tokens at the same endpoint. This approach is wrong.
In your login endpoint, validate the user and password against database. If credentials are correct we respond with an access token and a refresh token
router.post('/login',
asyncWrap(async (req, res, next) => {
const { username, password } = req.body
await validateUser(username, password)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
In your authenticated routes, you should validate only the access token (the user should send ONLY this one)
// middleware to validate the access token
export const validateToken = asyncWrap(async (req, res, next) => {
const data = await verifyAccessToken(req.headers.authorization)
req.auth = data
next()
})
If the access token expires, the user should refresh its token. In this endpoint we will validate the refresh token and respond with two new tokens:
router.post('/refresh',
asyncWrap(async (req, res, next) => {
const { refresh_token } = req.body
await verifyRefreshToken(refresh_token)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Using refresh tokens has nothing to do with security of the JWT or access token. Refresh tokens are just a UX feature. They allow you to get new access tokens without asking the user to authorize again. Having a refresh token in your app doesn't automatically make it more secure.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
When implemented this way the refresh token doesn't grant any more security to your application. You could as well keep the access tokens in the db and refresh them when they are expired.
Are you sure that you need JWTs at all? It looks like you're using them as you would use a session based on cookies. It should be simpler to deal with sessions. You are using http-only cookies for your tokens so you already use it pretty much like a session.
Now is this code correct & secure enough for a small webapplication?
Secure enough is a concept that depends on the data that your application has access to. If it's nothing sensitive, and you know that your app can't really be abused by an attacker, then it is fine to have only some basic security in place.
While implementing Auth0 Authentication/Authorization with a normal embedded login, I am able to authenticate the user and gets back the valid accessToken/idToken.
Initialization
webAuth = new auth0.WebAuth({
domain: 'xxx.auth0.com',
clientID: 'myclientid',
responseType: 'token id_token'
});
Successfully getting token.
webAuth.client.login({
realm: _Connection,
username: 'aaa#b.com',
password: 'password',
audience: 'https://xxx.auth0.com/api/v2/',
scope: 'openid profile email'
}, function (err, args) {
if (!err)
{
webAuth.client.userInfo(token, function (args, authUserData) {
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
***Setting bearer token to Global level.**
ajaxAdapter.defaultSettings = {
headers: ({ "Authorization": "Bearer " + token })
};
myAPICall(args.email).then({}).fail({});
});
}
});
Server code which is validating RS256 signed JWT with OWIN.
private void ConfigureAuthZero(IAppBuilder app)
{
var issuer = $"https://{ConfigurationManager.AppSettings["Auth0:Domain"]}/";
var audience = ConfigurationManager.AppSettings["Auth0:ClientID"];
var apiIdentifier = ConfigurationManager.AppSettings["Auth0:ApiIdentifier"];
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
string certificatePath = HostingEnvironment.MapPath("~/mycertificate.cer");
var certificate = new X509Certificate2(certificatePath);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => new X509SecurityKey(certificate)
}
});
}
My Problem:
The above server code won't authorize the user.
But if I set ValidAudience = "https://xxx.auth0.com/api/v2/" i.e to Auth0 API Identifier, then the API method successfully authorizes (status 200) the user.
But this time it won't give ClaimsIdentity.Claims with ClaimTypes.Email
What am I missing here?
My mistakes:
I should pass ApiIdentifier to ValidAudience value.
As I was passing accessToken while authorizing the user, by the
time the accessToken claims doesn't contain the ClaimTypes.Email, so
I need to set the rules in Auth0 as:How to set the rules in Auth0.
Which later I can check in my server api logic as(below code) to
validate the user.
(User.Identity as ClaimsIdentity)?.Claims.FirstOrDefault(c => c.Type == "you-have-set-this-rule-in-auth0")?.Value;
Just to Add-on, The Link worth reading while implementing the Auth0.
Auth0 has provided a nice nuget package Auth0.OpenIdConnectSigningKeyResolver which has a nice use in the above provided link.
I am using the JavaScript SDK of AWS Cognito (http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html).
When a new user completes registration confirmation, the documentation says the user is now ready to sign in. Is it possible to automatically sign in the user at this time?
For eg., after confirmation when I use the following I get null:
userPool.getCurrentUser();
If this is the intended behavior, are there any ways to sign in the user without explicitly asking the user again?
I know this is not a good idea, one thing I can think of is to save the user credentials in local storage and use them after confirmation to automatically sign in. Any other ideas better than this?
Upon user signup, your backend will be receiving users credentials, which you can use to generate the JWT token. Then you can add the JWT token in the same response, which can be use by the browser client to request authorized endpoints.
Example:
AWSCognito.config.region = 'us-east-1'; //This is required to derive the endpoint
var poolData = {
UserPoolId: 'us-east-1_TcoKGbf7n',
ClientId: '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: 'email#mydomain.com'
};
var authenticationData = {
Username: 'username',
Password: 'password',
};
var attributeEmail = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataEmail);
attributeList.push(attributeEmail);
userPool.signUp(authenticationData.Username, authenticationData.Password, attributeList, null, function (err, result) {
if (err) {
alert(err);
return;
}
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: authenticationData.Username,
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/*Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer*/
console.log('idToken + ' + result.idToken.jwtToken);
/*Return the result.idToken.jwtToken with the response*/
},
onFailure: function (err) {
alert(err);
},
});
});