AppSyncでは、Cognitoユーザープールを認証設定として使用すると、取得するIDが
identity:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
issuer: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
username: 'skillet',
claims:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
aud: '7re1oap5fhm3ngpje9r81vgpoe',
email_verified: true,
event_id: 'bb65ba5d-4689-11e8-bee7-2d0da8da81ab',
token_use: 'id',
auth_time: 1524441800,
iss: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
'cognito:username': 'skillet',
exp: 1524459387,
iat: 1524455787,
email: '[email protected]' },
sourceIp: [ '11.222.33.200' ],
defaultAuthStrategy: 'ALLOW',
groups: null }
ただし、AWS_IAM authを使用すると、
identity:
{ accountId: '12121212121', //<--- my Amazon account ID
cognitoIdentityPoolId: 'us-west-2:39b1f3e4-330e-40f6-b738-266682302b59',
cognitoIdentityId: 'us-west-2:a458498b-b1ac-46c1-9c5e-bf932bad0d95',
sourceIp: [ '33.222.11.200' ],
username: 'AROAJGBZT5A433EVW6O3Q:CognitoIdentityCredentials',
userArn: 'arn:aws:sts::454227793445:assumed-role/MEMORYCARDS-CognitoAuthorizedRole-dev/CognitoIdentityCredentials',
cognitoIdentityAuthType: 'authenticated',
cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7"' }
ドキュメントでは、これは想定されていると述べています https://docs.aws.Amazon.com/appsync/latest/devguide/resolver-context-reference.html 。ただし、Cognitoに接続されたAWS_IAM
を使用する場合(認証されていないアクセスが必要です)、ユーザーのユーザー名、電子メール、サブなどを取得するにはどうすればよいですか? AWS_IAM
タイプの認証を使用する場合、ユーザーのクレームにアクセスする必要があります。
これが私の答えです。 appSyncクライアントライブラリに、すべてのカスタムヘッダーを上書きするバグがありました。それは修正されました。これで、カスタムヘッダーを渡してリゾルバーに到達できるようになりました。これをラムダ関数に渡します(ここでも、ラムダデータソースを使用していて、dynamoDBを使用していないことに注意してください)。
そこで、ログインしたJWTをクライアント側に接続し、サーバー側でラムダ関数にそれをデコードします。 JWTを検証するには、cognitoによって作成された公開鍵が必要です。 (シークレットキーは必要ありません。)ラムダが最初に起動されたときにpingを実行するすべてのユーザープールに関連付けられた「既知のキー」のURLがありますが、mongoDB接続と同様に、ラムダ呼び出し間で永続化されます(少なくともしばらくの間。)
ここにラムダリゾルバがあります...
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;
}
}
これは、クライアント側にすでにある情報を取得するために常にDBにクエリを実行しているわけではないため、他の「悪い」答えよりもはるかに優れています。私の経験では約3倍速くなりました。
AppSync APIを介してユーザーのユーザー名、メール、サブなどにアクセスできるようにするための答えがあります: https://stackoverflow.com/a/42405528/120752
まとめると、ユーザープールIDトークンをAPI(AppSyncやAPI Gatewayなど)に送信する必要があります。 APIリクエストはIAM認証されています。次に、Lambda関数でIDトークンを検証します。これで、検証されたIAMユーザーとユーザープールのデータが一緒になります。
IAMのidentity.cognitoIdentityId
をユーザーテーブルの主キーとして使用します。 IDトークンに含まれるデータ(ユーザー名、電子メールなど)を属性として追加します。
これにより、APIを介してユーザーのクレームを利用できるようになります。これで、たとえば、アイテムの所有者として$ctx.identity.cognitoIdentityId
を設定できます。そうすれば、おそらく他のユーザーがGraphQLリゾルバーを介して所有者の名前を見ることができます。
リゾルバーでユーザーのクレームにアクセスする必要がある場合は、現時点では可能ではないようです。承認に非常に役立つので、これについて質問しました。 IAM認証を使用したAppSyncでのグループ承認
この場合、リゾルバーを使用する代わりに、Lambdaをデータソースとして使用し、上記のUserテーブルからユーザーのクレームを取得できます。
現時点では少し難しいです:)
これはうまくいく悪い答えです。 cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7"
には、Cognitoユーザーのサブ(CognitoSignInの後の大きい)が含まれています。これを正規表現で抽出し、aws-sdkを使用して、コグニトユーザープールからユーザーの情報を取得できます。
///////RETRIEVE THE AUTHENTICATED USER'S INFORMATION//////////
if(event.context.identity.cognitoIdentityAuthType === 'authenticated'){
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
//Extract the user's sub (ID) from one of the context indentity fields
//the REGEX in match looks for the strings btwn 'CognitoSignIn:' and '"', which represents the user sub
let userSub = event.context.identity.cognitoIdentityAuthProvider.match(/CognitoSignIn:(.*?)"/)[1];
let filter = 'sub = \"'+userSub+'\"' // string with format = 'sub = \"1a072f08-5c61-4c89-807e-417d22702eb7\"'
let usersData = await cognitoidentityserviceprovider.listUsers( {Filter: filter, UserPoolId: "us-west-2_KsyTKrQ2M",Limit: 1}).promise()
event.context.identity.user=usersData.Users[0];
}
JWTをデコードするだけでなく、ユーザープールデータベースにpingを送信するので、これは悪い答えです。
AWS Amplifyを使用している場合、これを回避するために私が説明したようにカスタムヘッダーusername
を設定することでした here のように:
Amplify.configure({
API: {
graphql_headers: async () => ({
// 'My-Custom-Header': 'my value'
username: 'myUsername'
})
}
});
それから私のリゾルバーで私はヘッダーにアクセスできます:
$context.request.headers.username
AppSyncのドキュメント ここ のセクションで説明されているようにAccess Request Headers
Honkskilletsの回答に基づいて、ユーザー属性を返すラムダ関数を記述しました。 JWTで関数を提供するだけです。
const jwt = require("jsonwebtoken");
const jwkToPem = require("jwk-to-pem");
const request = require("request-promise");
exports.handler = async (event, context) => {
try {
const { token } = event;
const decodedToken = jwt.decode(token, { complete: true });
const publicJWT = await request(
`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`
);
const keyObject = JSON.parse(publicJWT).keys.find(
key => key.kid == decodedToken.header.kid
);
const pem = jwkToPem(keyObject);
return {
statusCode: 200,
body: jwt.verify(token, pem)
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
body: error.message
};
}
};
私はそれをAppsyncで使用してパイプラインリゾルバーを作成し、ユーザー属性が必要なときにこの関数を追加します。 JWTは、$context.request
を使用してリゾルバーのヘッダーから取得することで提供します。