AppSync: Get user information in $context when using AWS_IAM auth

后端 未结 5 1556
迷失自我
迷失自我 2020-12-24 15:47

In AppSync, when you use Cognito User Pools as your auth setting your identity you get

identity: 
   { sub: \'bcb5cd53-315a-40df-a41b-1db02a4c1bd9\',
     is         


        
5条回答
  •  再見小時候
    2020-12-24 16:17

    Here is my answer. There was a bug in the appSync client library that would overwrite all custom headers. That has since been fixed. Now you can pass down custom headers that will make it all the way to you resolvers, which I pass to my lambda functions (again, note I am using lambda datasourcres and not using dynamoDB).

    So I attach my logged in JWT on the client side and, server side in my lambda function, I decode it. You need the public key created by cognito to validate the JWT. (YOU DO NOT NEED A SECRET KEY.) There is a "well known key" url associated with every user pool which I ping the first time my lambda is spun up but, just like my mongoDB connection, it is persisted between lambda calls (at least for a while.)

    Here is lambda resolver...

    const mongoose = require('mongoose');
    const jwt = require('jsonwebtoken');
    const jwkToPem = require('jwk-to-pem');
    const request = require('request-promise-native');
    const _ = require('lodash')
    
    //ITEMS THAT SHOULD BE PERSISTED BETWEEN LAMBDA EXECUTIONS
    let conn = null; //MONGODB CONNECTION
    let pem = null;  //PROCESSED JWT PUBLIC KEY FOR OUR COGNITO USER POOL, SAME FOR EVERY USER
    
    exports.graphqlHandler =  async (event, lambdaContext) => {
        // Make sure to add this so you can re-use `conn` between function calls.
        // See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas
        lambdaContext.callbackWaitsForEmptyEventLoop = false; 
    
        try{
            ////////////////// AUTHORIZATION/USER INFO /////////////////////////
            //ADD USER INFO, IF A LOGGED IN USER WITH VALID JWT MAKES THE REQUEST
            var token = _.get(event,'context.request.headers.jwt'); //equivalen to "token = event.context.re; quest.headers.alexauthorization;" but fails gracefully
            if(token){
                //GET THE ID OF THE PUBLIC KEY (KID) FROM THE TOKEN HEADER
                var decodedToken = jwt.decode(token, {complete: true});
                // GET THE PUBLIC KEY TO NEEDED TO VERIFY THE SIGNATURE (no private/secret key needed)
                if(!pem){ 
                    await request({ //blocking, waits for public key if you don't already have it
                        uri:`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`,
                        resolveWithFullResponse: true //Otherwise only the responce body would be returned
                    })
                        .then(function ( resp) {
                            if(resp.statusCode != 200){
                                throw new Error(resp.statusCode,`Request of JWT key with unexpected statusCode: expecting 200, received ${resp.statusCode}`);
                            }
                            let {body} = resp; //GET THE REPSONCE BODY
                            body = JSON.parse(body);  //body is a string, convert it to JSON
                            // body is an array of more than one JW keys.  User the key id in the JWT header to select the correct key object
                            var keyObject = _.find(body.keys,{"kid":decodedToken.header.kid});
                            pem = jwkToPem(keyObject);//convert jwk to pem
                        });
                }
                //VERIFY THE JWT SIGNATURE. IF THE SIGNATURE IS VALID, THEN ADD THE JWT TO THE IDENTITY OBJECT.
                jwt.verify(token, pem, function(error, decoded) {//not async
                    if(error){
                        console.error(error);
                        throw new Error(401,error);
                    }
                    event.context.identity.user=decoded;
                });
            }
            return run(event)
        } catch (error) {//catch all errors and return them in an orderly manner
            console.error(error);
            throw new Error(error);
        }
    };
    
    //async/await keywords used for asynchronous calls to prevent lambda function from returning before mongodb interactions return
    async function run(event) {
        // `conn` is in the global scope, Lambda may retain it between function calls thanks to `callbackWaitsForEmptyEventLoop`.
        if (conn == null) {
            //connect asyncoronously to mongodb
            conn = await mongoose.createConnection(process.env.MONGO_URL);
            //define the mongoose Schema
            let mySchema = new mongoose.Schema({ 
                ///my mongoose schem
            }); 
            mySchema('toJSON', { virtuals: true }); //will include both id and _id
            conn.model('mySchema', mySchema );  
        }
        //Get the mongoose Model from the Schema
        let mod = conn.model('mySchema');
        switch(event.field) {
            case "getOne": {
                return mod.findById(event.context.arguments.id);
            }   break;
            case "getAll": {
                return mod.find()
            }   break;
            default: {
                throw new Error ("Lambda handler error: Unknown field, unable to resolve " + event.field);
            }   break;
        }           
    }
    

    This is WAY better than my other "bad" answer because you are not always querying a DB to get info that you already have on the client side. About 3x faster in my experience.

提交回复
热议问题