ã¯ããã« ããã«ã¡ã¯ããããªãã¯ã»ã¯ã¿ãŒã®ã客æ§åãã«ãããã¿ã€ãã³ã°ã®æ¯æŽãããŠãããSA ã®éŽæšã§ãã æ©éã§ãããã¿ãªããŸãããžã¿ã«åºãæäŸããŠãããããžã¿ã«èªèšŒã¢ããªã¯ãåç¥ã§ããããïŒ ãããžã¿ã«èªèšŒã¢ããªã ã¯ããã€ãã³ããŒã«ãŒãã䜿ã£ãæ¬äººç¢ºèªããå®å
šã«ã»ç°¡åã«ããããã®ã¢ããªã§ãã 什å6幎ïŒ2024幎ïŒ4ææç¹ã§ããã€ãã³ããŒã«ãŒãã®ä¿æçã¯70%ãè¶ããŠããããã€ãã³ããŒã«ãŒãã®å©çšã·ãŒã³ãåºãã£ãŠããŸãããããžã¿ã«èªèšŒã¢ããªãã¯ããã€ãã³ããŒã«ãŒãã䜿ã£ãèªèšŒã眲åããå®å
šã«ã»ç°¡åã«ããããã®ãããžã¿ã«åºãæäŸããã¢ããªã§ãã è¡æ¿æ©é¢ãæ°éäºæ¥è
ã¯ãããžã¿ã«åºãæäŸããããžã¿ã«èªèšŒã¢ããªãšé£æºãã APIïŒããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ APIïŒã掻çšããããšã§ããã€ãã³ããŒã«ãŒãã䜿ã£ãæ¬äººç¢ºèªã»èªèšŒãé»åç³è«æžé¡ãžã®çœ²åæ©èœãç°¡åã«çµã¿èŸŒãããšãã§ããŸãã åŒçšå
ïŒããžã¿ã«èªèšŒã¢ã㪠â https://services.digital.go.jp/auth-and-sign/business/ ããžã¿ã«èªèšŒã¢ããªãšé£æºãããããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãå©çšãããšãEC ãµã€ãã»ããããã³ãã³ã°ã®å©çšéå§æã«çºçããæ¬äººç¢ºèªããã€ãã³ãçã§ã®é
é¡è³Œå
¥æã®å¹Žéœ¢ç¢ºèªãå°åã«äœãã§ããæ¹ã®äœæç¢ºèªããšãã£ãããšããã€ãã³ããŒã«ãŒããçšããŠè¡ãããšãã§ããŸãã ãããŸã§ãã®ãããªæ¬äººç¢ºèªã¯ãç³è«æžé¡ãšå
±ã«å
蚱蚌ãå¥åº·ä¿éºèšŒã®ã³ããŒãåå°ãããããæžé¡ãç³è«å
ã§ç¢ºèªãåé¡ãªããã°ç³è«ãåçãããããšãã£ãæµãã§é²ã¿ãç³è«ãåçããããŸã§ãæ°æ¥ãã1é±éè¿ãããã£ãŠããŸãããããã¯éµéã«ããé·ããªãŒãã¿ã€ã ãæäœæ¥ã«ãã確èªäœæ¥ããšã³ã¹ããé«ããã®ã§ãããããããããžã¿ã«èªèšŒã¢ããªãå©çšããããšã§ããªã³ã©ã€ã³ã§æ¬äººç¢ºèªãæžãŸããããšãã§ããããçæéã§ã®ç³è«ããç³è«ç¢ºèªäœæ¥ã®å¹çåãå¯èœã«ãªããŸãã æ¬èšäºã®ãŽãŒã« æ¬èšäºã§ã¯ããµãŒãã¹ãµã€ãã瀺ããæ¬¡ã® ãèªèšŒ API å©çšã®æµãã ããCognito ãšé£æºããå®çŸããããšæããŸãã æ¬äººç¢ºèªãå¿
èŠãªãµãŒãã¹ãäŒç»ã»éçºãããŠããæ¹ã
åãã«ã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãšãAWS ã® Customer Identity and Access Management (CIAM) ã®ãµãŒãã¹ã§ãã Amazon Cognito ã®é£æºã®äžäŸãšã㊠ãã²ã確èªããã ããã°ãšæããŸãã èªèšŒ API å©çšã®æµãïŒåŒçšå
ïŒãæ°éäºæ¥è
åãæ
å ±ããã€ãã³ããŒã«ãŒãã§æ¬äººã®ç¢ºèªãç°¡åã« â https://services.digital.go.jp/auth-and-sign/business/ ïŒ Cognito 㯠ãœãŒã·ã£ã«ãªIdentity Provider (IdP) ããOpen ID Connect (OIDC) ãSecurity Assertion Markup Language (SAML) ãšãã£ããµãŒãããŒãã£ã® IdP ãçµç±ããèªèšŒãå¯èœã§ãããã®ãããCognito ãå©çšããOIDC ã«å¯Ÿå¿ããŠããããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãšã飿ºããããšãçè«äžå¯èœã§ããããããããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã§æ±ããããŠãã å®è£
ã®ã¬ã€ãã©ã€ã³ ãæºããã«ã¯ãprivate key jwt èªèšŒãå©çšããå¿
èŠããããŸãã2025幎2æçŸåšãCognito ã¯ãprivate key jwt èªèšŒã«é察å¿ã§ãããã®ãããä»å㯠client secret ã®ä»£ããã«ãprivate key jwt èªèšŒã Cognito ã§å©çšã§ããããã«ãããµã³ãã«å®è£
ã§ããã cognito-external-idp-proxy ãã«ã¹ã¿ãã€ãºããŠå©çšããŸããæ¬ãµã³ãã«ã¯ã ããã° ãå
¬éãããŠããŸãã®ã§ããŸããã¡ãããäžèªããã ãããšããå§ãããŸãã ç®ãéããŠããã ããããšããåæã®äžã§ãå
ã«é²ããããšæããŸããæ¬¡ã«ç€ºãæ§æå³ã cognito-external-idp-proxy ãå©çšããå Žåã®å
šäœæ§æãšãªããŸãã ãŸãããã®æ§æã®ã·ãŒã±ã³ã¹å³ã¯ä»¥äžã®ããã«ãªããŸãã â»äžèšã¯ã ããžã¿ã«åº éçºè
ãµã€ãã®ã·ãŒã±ã³ã¹å³ïŒèªèšŒ ãš cognito-external-idp-proxy ã®ããã¥ã¡ã³ã ãå
ã«äœæããŸããã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã®å©çšã«ããã£ãŠ ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã¯ãå©çšã«ãããäºåã«ç³è«ãå¿
èŠã§ãã詳ããã¯ã ããžã¿ã«åºãå
¬éããŠãããµãŒãã¹ãµã€ã ããäžèªããã ããç³è«ããŠãã ãããç³è«ã®éã«ã¯ãç³è«ããäŒæ¥ã®æ
å ±ãªã©ãå¿
èŠã«ãªããç¹ã«ãã·ã¹ãã é¢ã§äºåã«æ³šæãã¹ãé
ç®ãšããŠã¯ã次ã«ç€ºããã®ããããŸãã private key jwt èªèšŒçšã®ç§å¯éµãšå
¬ééµã®ã㢠cognito-external-idp-proxy ã® README ã«äœææé ãèšèŒãããŠããŸãã ããã§äœæããå
¬ééµãããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã®å©çšç³è«æã«æåºããŸãããã¢ãšãªãç§å¯éµã¯å€§äºã«ä¿ç®¡ããŠãã ããã ã³ãŒã«ãã㯠URL cognito-external-idp-proxy ããããã€åŸã«æäŸããããã³ãŒã«ããã¯é¢æ°ãšæ¥ç¶ãããŠãã API Gateway ã®ãšã³ããã€ã³ããç³è«æã«æåºããããšã«ãªããŸãã ã«ã¹ã¿ã ãã¡ã€ã³ãå©çšããªãå Žåããã¡ã€ã³åã¯ãããã€åŸã«æ±ºå®ãããŸããå
ãããŠãããã€ãé²ããŠãããŸãããã ãã¹ãã«ãŒã/ãã¹ãã«ãŒãä»£æ¿æ©èœã®æºå J-LIS ã«å¥éç³è«ããå¿
èŠããããŸãã貞äžãŸãã¯è³Œå
¥ããéžæã§ããŸãã ä»åã¯ãã¹ãã«ãŒãã J-LIS ãã貞äžããŠããã ããæ€èšŒãè¡ããŸãããããã¹ãã«ãŒãä»£æ¿æ©èœãçšããæ€èšŒãå¯èœã«ãªã£ãããã§ãããã¹ãã«ãŒãã®ç®¡çãç
©éãªå Žåãæ¥ãæ€èšŒãããå Žåã¯ããã®æ©èœã®å©çšãæ€èšãã ããã https://developers.digital.go.jp/documents/auth-and-sign/testcard_alternative/ ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã®å®è£
ã¬ã€ãã©ã€ã³ãšãµã³ãã«ã®æ¯èŒ ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãå©çšããããã«å¿
èŠãªå®è£
ã¯ã å®è£
ã®ã¬ã€ãã©ã€ã³ ïŒæ¬èšäºã§ã¯ã2025幎3æ27æ¥æç¹ãåç
§ïŒ ã«ãŸãšããããŠããŸãããããæŽçããcognito-external-idp-proxy ã®å®è£
ç¶æ³ãšæ¯èŒãããšã次ã®ãããªè¡šã«ãŸãšããããšãã§ããŸããïŒæšªé·ã«ãªã£ãŠããŸããèªã¿ã¥ããã§ããã容赊ãã ããïŒ èŠä»¶ èŠä»¶è©³çް é
ç®åé¡ é
ç®ãšãã®æŠèŠ ãµã³ãã«ã§å®è£
æžã¿ãã©ãã æ¬èšäºã§è¿œå ã»å€æŽããã å®è£
ç®æãŸãã¯ã远å å®è£
æ¹éã®æŠèŠ ãªãã€ã¬ã¯ã URI ã®èšå® 以äžã®èª¬æã確èªã®ãããããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ãžã®ç³èŸŒæã«ãå RP ã¢ããªãžã®ãªãã€ã¬ã¯ã URI ãæå®ããŠãã ããã RP ããã€ãã£ãã¢ããªã®å Žå iOS㯠Universal LinkãAndroid㯠App Link ãæå®ããŠãã ããã æªæã®ããä»ã¢ããªã«ããä¹ã£åãæ»æã®å±éºããããããCustom URL Schema ã¯æå®ããªãã§ãã ããã RP ã WEB ã¢ããªã®å Žå ãªãã€ã¬ã¯ã URI ã« WEB ã¢ããªã® URI ãæå®ããããšã§ãWEB ã¢ããªäžã§èªå¯ã¬ã¹ãã³ã¹ãååŸã§ããŸãã RP ããã©ãŠã¶ããŒã¹ã¢ããªã®å Žå ããŒã¯ã³ãå®å
šã«åãæ±ããããBFF ã¢ãŒããã¯ãã£ïŒBackend For FrontendïŒãæ¡çšããŠãã ããã ãªãã€ã¬ã¯ã URI ã« BFF ã® URI ãæå®ããŠãã ããã ãªãã€ã¬ã¯ã URI ã®èšå® RP ã WEB ã¢ããªã®å Žå ãªãã€ã¬ã¯ã URI ã« WEB ã¢ããªã® URI ãæå®ããããšã§ãWEB ã¢ããªäžã§èªå¯ã¬ã¹ãã³ã¹ãååŸã§ããŸãã å®è£
æžã¿ ããã Cognito + cognito-external-idp-proxy ã®éšåã RP ãšããŠæ±ãããšã§ã RP ã WEB ã¢ããªã®å Žå ãšå矩ãšããŠæããŠããŸãã ID ããŒã¯ã³ã®æ€èšŒ èªå¯ã³ãŒããããŒã®ããŒã¯ã³ã¬ã¹ãã³ã¹ã«ã¯ãID ããŒã¯ã³ãå«ãŸããŠããŸãã â»çœ²åããã»ã¹ã«ãããŠã¯ã©ã€ã¢ã³ãã¯ã¬ãã³ã·ã£ã«ãºãããŒãæ±ãããéçšããããŸãããã¯ã©ã€ã¢ã³ãã¯ã¬ãã³ã·ã£ã«ãºãããŒã«ãããŠã¯ ID ããŒã¯ã³ã¯å«ãŸããŸããã RP ã¢ããªã¯ãå©çšè
èªèšŒæ
å ±ãå«ãæ¹ããæ€ç¥çšã®çœ²åä»ãããŒã¯ã³ã§ãã ID ããŒã¯ã³ã«ã€ããŠãããŒã¯ã³ã¬ã¹ãã³ã¹ååŸåŸã«å¿
ãæ£åœæ§ãæ€èšŒããŠãã ããã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ã® JWT ã®çœ²åã¢ã«ãŽãªãºã 㯠ES256 ã§ãã JWT ã¯ããªãªãïŒâ.âïŒåºåãã®ãããéšããã€ããŒãéšãã·ã°ããã£ãŒéšããæ§æãããåéšäœã¯ Base64URL ãšã³ã³ãŒããããŠããŸãã æ€èšŒæºå ååŸãã ID ããŒã¯ã³ã Base64URL ãã³ãŒããããããéšããã€ããŒãéšãã·ã°ããã£ãŒéšãïŒâ.âïŒã§åå²ããŸãã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ã® JWK Set å
¬éãšã³ããã€ã³ããçšããŠãããžã¿ã«èªèšŒã¢ããªãµãŒãã¹å
ã®äžé£ã®å
¬é鵿
å ±ãååŸããŸããå
¬ééµã¯å®æçã«æŽæ°ããããããID ããŒã¯ã³æ€èšŒã®åºŠã«ååŸããŠãã ããã å®è£
æžã¿ ããã Cognito ã§æ€èšŒããŠããŸãã 該åœéšåã®åŒçšã§ãã ãŠãŒã¶ãŒããŒã«ã¯ãIdP èšå®ã®çºè¡è
URLs ãã IdP jwks_uri ãšã³ããã€ã³ããžã®ãã¹ã決å®ããJSON ãŠã§ãããŒã»ãã (JWKS) ãšã³ããã€ã³ãããããŒã¯ã³çœ²åããŒããªã¯ãšã¹ãããŸããIdP 㯠JWKS ãšã³ããã€ã³ããã眲åããŒãè¿ããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html JWT ãããéšã®æ€èšŒ ID ããŒã¯ã³ãããéšã® kid ãæ€èšŒæºåã§ååŸããå
¬ééµã® kid ãšäžèŽããããšãæ€èšŒããŠãã ããã 眲åã®ã¢ã«ãŽãªãºã ã§ãã alg ãšãæ€èšŒæºåã§ååŸããå
¬ééµã®ã¢ã«ãŽãªãºã ããšãã«ãES256ãã«ãªã£ãŠããããšãæ€èšŒããŠãã ããã æªå®è£
ã¯ã cognito-external-idp-proxy ã® token Lambda 颿°ã§å®è£
ããŸãã PyJWT ã§æäŸãããŠãã颿°ãçšããŠããããéšã®æ€èšŒãè¡ããŸãã 眲åã®æ€èšŒ RP ã¢ããªã§ã䜿çšã®éçºèšèªãã䜿çšãããã©ã€ãã©ãªçã«ãã£ãŠç°ãªããŸãã æ€èšŒã®æŠèŠã以äžã«ç€ºããŸãã Base64URL ãšã³ã³ãŒãç¶æ
ã®ãããéš + â.â + ãã€ããŒãéšãããŒã¿éšãšããŠä¿æ ã»ã»ã»å€æ°â Base64URL ãã³ãŒãããã·ã°ããã£ãŒéšãä¿æ ã»ã»ã»å€æ°â¡ æ€èšŒæºåã§ååŸããå
¬ééµãä¿æ ã»ã»ã»å€æ°â¢ ã¢ã«ãŽãªãºã ã ES256 ãšããŠä¿æ ã»ã»ã»å€æ°â£ 倿°â ïœâ£ã䜿çšããéçºèšèªãŸãã¯äœ¿çšãããã©ã€ãã©ãªã§çœ²åãæ€èšŒ æ€èšŒçµæã®æ£åžžã»ç°åžžã倿 å®è£
æžã¿ ããã Cognito ã§æ€èšŒããŠããŸãã 該åœéšåã®åŒçšã§ãã ID ããŒã¯ã³ã®çœ²åãããããã€ããŒã®ã¡ã¿ããŒã¿ã«åºã¥ããŠæ³å®ããã眲åãšæ¯èŒããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html ãã€ããŒãéšã®æ€èšŒ ID ããŒã¯ã³ã®çºè¡è
ã瀺ã Issuer Identifierã¯ã¬ãŒã ïŒissïŒã OpenID Provider ã¡ã¿ããŒã¿ã® issuer ã®å€ãšäžèŽããããšãæ€èšŒããŠãã ããã å®è£
æžã¿ ããã Cognito ã§æ€èšŒããŠããŸãã 該åœéšåã®åŒçšã§ãã iss ã¯ã¬ãŒã ã IdP ã«èšå®ããã OIDC çºè¡è
ãšæ¯èŒããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html ID ããŒã¯ã³ã® audience ã¯ã¬ãŒã ïŒaudïŒã«ãRP ã¢ããªã®ã¯ã©ã€ã¢ã³ã ID ãå«ãŸããŠããããšãæ€èšŒããŠãã ããã å®è£
æžã¿ ããã Cognito ã§æ€èšŒããŠããŸãã 該åœéšåã®åŒçšã§ãã aud ã¯ã¬ãŒã ã IdP ã§èšå®ãããŠããã¯ã©ã€ã¢ã³ã ID ãšäžèŽãããããŸã㯠aud ã¯ã¬ãŒã ã«è€æ°ã®å€ãããå Žåã¯èšå®ãããã¯ã©ã€ã¢ã³ã ID ãå«ãŸããŠããããæ¯èŒããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html ID ããŒã¯ã³ã®æå¹æéã瀺ã expiration time ã¯ã¬ãŒã ïŒexpïŒããæ€èšŒæã®æéããæªæ¥ã§ããããšãæ€èšŒããŠãã ããã å®è£
æžã¿ ããã Cognito ã§æ€èšŒããŠããŸãã 該åœéšåã®åŒçšã§ãã exp ã¯ã¬ãŒã ã®ã¿ã€ã ã¹ã¿ã³ããçŸåšã®æå»ããåã§ãªãããšããã§ãã¯ããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html ID ããŒã¯ã³ã®çºè¡æ¥æã瀺ã Issued at ã¯ã¬ãŒã ïŒiatïŒãã æ€èšŒæã® UNIX ã¿ã€ã ã¹ã¿ã³ãå€ ïŒ æèŠæéïŒç§ïŒ ã®å€ä»¥äžã§ããããšãæ€èšŒããŠãã ããã æèŠæéã¯ãRP ã¢ããªã§æ±ºããå¿
èŠããããŸãã nonce ä¿åãã RP ã¢ããªã ID ããŒã¯ã³åãåããŸã§ã®æéãç®å®ã§ãã æå®æéãçµéããŠããå Žåã¯å¿
èŠã«å¿ããŠãRP ã¢ããªåŽã«ãŠåèªèšŒã宿œãããªã©ã远å åŠçã®å®è£
ãæ€èšããŠãã ããã æªå®è£
ã¯ã cognito-external-idp-proxy ã® token Lambda 颿°ã§å®è£
ããŸãã PyJWT ã§æäŸãããŠãã decode 颿°ã§ iat ã¯ã¬ãŒã ãæ€èšŒããŠããããããããå©çšããŸãã ID ããŒã¯ã³ã® at_hash ã¯ã¬ãŒã ã«ã€ããŠã以äžã®ãšããæ€èšŒããŠãã ããã ã¢ã¯ã»ã¹ããŒã¯ã³ããID ããŒã¯ã³ãããéšã® algorithm ã¯ã¬ãŒã ïŒalgïŒãšåãããã·ã¥ã¢ã«ãŽãªãºã ãçšããŠããã·ã¥åããããã·ã¥åããçµæã®å·Šååã®ããã矀ã Base64URL ã«ãŠãšã³ã³ãŒãããããã§ãID ããŒã¯ã³ã® at_hash ãšå€ãäžèŽããããšã確èªããŠãã ããã æªå®è£
ã¯ã cognito-external-idp-proxy ã® token Lambda 颿°ã§å®è£
ããŸãã PyJWT ã§æäŸãããŠãã decode 颿°ããè¿ããã ID ããŒã¯ã³ãã at_hash ãååŸããã¢ã¯ã»ã¹ããŒã¯ã³ããçæãã at_hash ãšæ¯èŒããããšã§ãæ€èšŒããŸãã æ€èšŒå€±ææ ã¿ã€ã ã¹ã¿ã³ãã®æ€èšŒã§å€±æããå Žåã¯ãæå¹æéåããèªèšŒæå»ãéããŠãããããèªèšŒåŠçãäžæããããŒã¯ã³ãªãã¬ãã·ã¥ã«ãŠ ID ããŒã¯ã³ã®åçºè¡ãè¡ã£ãŠãã ããã æªå®è£
ããã æ¬èšäºã§ã¯å®è£
ããŸããããexp, iat, nbf ã¯ã¬ãŒã ã®æ€èšŒåŸãæå¹æéåãã§ããã°ãããŒã¯ã³ãªãã¬ãã·ã¥ãè¡ãããã«å®è£
ããŸãã ãã®ä»ã®æ€èšŒã§å€±æããå Žåã¯ãID ããŒã¯ã³ãæ¹ãããããŠããå¯èœæ§ããããããèªèšŒåŠçãäžæãããšã©ãŒåŠççãè¡ã£ãŠãã ããã æªå®è£
ã¯ã exp, iat, nbf ã¯ã¬ãŒã ã®ã¿ã€ã ã¹ã¿ã³ãã«é¢ããäŸå€ä»¥å€ã¯ãèªèšŒå€±æã§ã¬ã¹ãã³ã¹ãè¿ãããå®è£
ããŸãã logout ããŒã¯ã³ã®æ€èšŒ ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ãã RP ã¢ããªãž back-channel logout ãªã¯ãšã¹ããéä¿¡ããéãlogout ããŒã¯ã³ã POST ãã©ã¡ãŒã¿ãžå«ããŸãã RP ã¢ããªã¯ãlogout ããŒã¯ã³ã«ã€ããŠæ£åœæ§ã®æ€èšŒãå«ããŠãå©çšè
ãšã®ã»ãã·ã§ã³çµäºåŠçã宿œããŠãã ããã logout ããŒã¯ã³ã®æ€èšŒ åå·Š æªå®è£
ããã èšå®ãä»»æã®ãããä»åã¯çç¥ããŸãããæ¬æ¥ã¯ RP åŽã§ãšã³ããã€ã³ããå®è£
ãããã®ãšã³ããã€ã³ãã®ããžãã¯ã§ logout ããŒã¯ã³ã«ã€ããŠã®æ£åœæ§ã®æ€èšŒãšã»ãã·ã§ã³çµäºåŠçã宿œããå¿
èŠããããŸãã ID ããŒã¯ã³çœ²åæ€èšŒçšå
¬ééµã®ããŒããŒã·ã§ã³ãèæ
®ããæºå 眲åãæ€èšŒããããã®å
¬ééµã¯ãæå·æŒãããçãããå Žåãæ³å®ããæå¹æéå
ã§ãã£ãŠãããŒããŒã·ã§ã³ããå¯èœæ§ããããŸããRP ã¢ããªã«ä¿æããå
¬ééµã«ã€ããŠãããŒå€æŽãèªåçã«åŠçããããã« RP ã¢ããªãäœæããŠãã ããã å
¬ééµã®ããŒããŒã·ã§ã³ JWK Set å
¬éãšã³ããã€ã³ã https://auth-and-sign.go.jp/api/realms/main/protocol/openid-connect/certs OpenID Provider Configuration ãšã³ããã€ã³ã https://auth-and-sign.go.jp/api/realms/main/.well-known/openid-configuration æªå®è£
ã¯ã ID ããŒã¯ã³ã®æ€èšŒã token Lambda 颿°ã§å®è£
ããå¿
èŠããããããå
¬ééµã®ããŒããŒã·ã§ã³ã«ã€ããŠãèæ
®ããå¿
èŠããããŸãã PyJWT ã©ã€ãã©ãªãçšããŠããªã¯ãšã¹ãããšã«å
¬ééµãååŸããŸãã â»æ¬èšäºã§ã¯ãå
¬ééµã®ãã£ãã·ã¥ã¯æªå®è£
ã§ãããå®éã«å©çšãããå Žåã¯ãå
¬ééµããã£ãã·ã¥ããä»çµã¿ã®å®è£
ããå§ãããããŸãã ãŸããCognito ã«ãããæ€èšŒã¯ä»¥äžã®ããã«ãªã£ãŠããŸãã 該åœéšåã®åŒçšã§ãã ãŠãŒã¶ãŒããŒã«ã¯ãIdP èšå®ã®çºè¡è
URLs ãã IdP jwks_uri ãšã³ããã€ã³ããžã®ãã¹ã決å®ããJSON ãŠã§ãããŒã»ãã (JWKS) ãšã³ããã€ã³ãããããŒã¯ã³çœ²åããŒããªã¯ãšã¹ãããŸãã IdP 㯠JWKS ãšã³ããã€ã³ããã眲åããŒãè¿ããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html Web ã¢ããªã±ãŒã·ã§ã³ã®èåŒ±æ§æªçšãžã®å¯Ÿç æªæã®ãããªã¯ãšã¹ããéä¿¡ãããããšãç®çãšããã¯ãã¹ãµã€ããªã¯ãšã¹ããã©ãŒã·ãã§ãªïŒCSRFïŒãžã®å¯ŸçãããŠãã ããã æ€èšŒçµæãäžèŽããªãå Žå㯠CSRF ã«ããæ»æã®å¯èœæ§ããããããèªèšŒåŠçãäžæãããšã©ãŒåŠççãè¡ã£ãŠãã ããã RP ã¢ããªã®å¯Ÿå¿æ¹æ³ïŒCSRFïŒ RP ã¢ããªã¯ãstate ãšããã©ã³ãã ãªå€ãçæããŠãèªå¯ãªã¯ãšã¹ãã«èšå®ããå©çšè
ã®ã»ãã·ã§ã³ã«ããã®å€ãä¿åããŠãã ããã state ã«èšå®ããå€ã«ã€ããŠã¯ãå¥çŽã® API ãªãã¡ã¬ã³ã¹ããåç
§ãã ããã å®è£
æžã¿ ããã state ã¯ä»¥äžã®ã³ãŒãã§ä¿æããããå®è£
ãããŠããŸãã https://github.com/aws-samples/cognito-external-idp-proxy/blob/d85fc2bed25627170fe5989b3b8d344d9b1ab860/lambda/authorize/authorize_flow.py#L82 å¿
ããèªå¯ãªã¯ãšã¹ãããšã«æ°ãã state å€ãçæããŠãã ããã å®è£
æžã¿ ããã Cognito ããµãŒãããŒãã£ãŒ ID ãããã€ããŒãžèªå¯ãªã¯ãšã¹ããéãéã«ãstate ãçæããŠããŸãã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¯ãèªå¯ã³ãŒããå«ããªãã€ã¬ã¯ããã¬ã¹ãã³ã¹ããéã«ãstate ã®å€ã RP ãžã¬ã¹ãã³ã¹ããŸãã RP ã¢ããªãçæãã state ãšãããžã¿ã«èªèšŒã¢ããªãµãŒããããªãã€ã¬ã¯ãã§éããã state ãäžèŽããããšãæ€èšŒããŠãã ããã å®è£
æžã¿ ããã https://github.com/aws-samples/cognito-external-idp-proxy/blob/d85fc2bed25627170fe5989b3b8d344d9b1ab860/lambda/callback/callback_flow.py#L65 äžèšåŠçã§ãã§ãã¯ããŠããŸãã ãªãã¬ã€æ»æãžã®å¯Ÿç å©çšè
ã®ãã°ã€ã³ã»ãã·ã§ã³ç¢ºç«ã®éã«ãæ»æè
ã ID ããŒã¯ã³ã眮ãæããŠäžæ£ãã°ã€ã³ãããªãã¬ã€æ»æãžã®å¯ŸçãããŠãã ããã æ€èšŒçµæãäžèŽããªãå Žåã¯ãªãã¬ã€æ»æã®å¯èœæ§ããããããèªèšŒåŠçãäžæãããšã©ãŒåŠççãè¡ã£ãŠãã ããã RP ã¢ããªã®å¯Ÿå¿æ¹æ³ïŒnonceïŒ RP ã¢ããªã¯ãnonce ãšããã©ã³ãã ãªå€ãçæããŠãèªå¯ãªã¯ãšã¹ãã«èšå®ããå©çšè
ã®ã»ãã·ã§ã³ã«ããã®å€ãä¿åããŠãã ããã nonce ã«èšå®ããå€ã«ã€ããŠã¯ãå¥çŽã® API ãªãã¡ã¬ã³ã¹ããåç
§ãã ããã å¿
ããèªå¯ãªã¯ãšã¹ãããšã«æ°ãã nonce å€ãçæããŠãã ãããClient ã»ãã·ã§ã³ãš ID Token ãçŽã¥ããæååããªãã¬ã€ã¢ã¿ãã¯å¯Ÿçã«çšããããã nonce å€ã¯ãcase-sensitive (倧æåãšå°æåãåºå¥ãã) ã§ããç¹ã«çæããããšã( OpenID Connect ) ãªã¯ãšã¹ãæ¯ã«ãã³ã¹å€ãçæãèšå®ããã code_verifier ãšåæ§ã«ã256 ãããã®ãšã³ããããŒãæšå¥šã æªå®è£
ã¯ã cognito-external-idp-proxy ã® authorize Lambda 颿°ã§ãnonce ãçæããDynamoDB ã§ä¿æããããå®è£
ããå¿
èŠããããŸãã åãããŠãnonce ã®ã¬ã³ãŒãã« TTL ãèšå®ããnonce ãèªåçã«åé€ãããããã«ããŸãã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¯ãã¢ã¯ã»ã¹ããŒã¯ã³çºè¡ã¬ã¹ãã³ã¹ãè¿åŽããéãRP ã¢ããªãçæãã nonce ãå«ã ID ããŒã¯ã³ãè¿åŽããŸãã RP ã¢ããªã¯ãè¿åŽããã nonce ãšèªèº«ãçæãã nonce ãäžèŽããããšãæ€èšŒããŠãã ããã æªå®è£
ã¯ã Cognito ã¯ãèªå¯ãªã¯ãšã¹ãã« nonce ãã©ã¡ãŒã¿ãæž¡ããš ID ããŒã¯ã³ã® nonce ãã©ã¡ãŒã¿ãæ€èšŒã§ããŸãã https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authorization-endpoint.html Lambda ã§èªå¯ãªã¯ãšã¹ããéãéã«ãnonce ãã©ã¡ãŒã¿ãèšå®ããããšã§æ€èšŒããŸãã èªå¯ã³ãŒã暪åãæ»æãžã®å¯Ÿç èªå¯ã¬ã¹ãã³ã¹åä¿¡æã®ãªãã€ã¬ã¯ãåŠçã«ãããæªæã®ããã¢ããªãžã®ãªãããŸã察çãããŠãã ããã RP ã¢ããªã®å¯Ÿå¿æ¹æ³ïŒPKCEïŒ RP ã¢ããªã¯ãèªå¯ãªã¯ãšã¹ãã« code_challengeãcode_challenge_method ãã©ã¡ãŒã¿ãèšå®ããŠãã ããã å¿
ããèªå¯ãªã¯ãšã¹ãããšã«æ°ãã code_challenge å€ãçæããŠãã ããã code_challenge ã®çææ¹æ³ãšãªã code_challenge_method ã®å€ã«ã€ããŠã¯ãå¿
ããS256ããæå®ããŠãã ããã code_challenge ããã³ code_challenge_method ã«èšå®ããå€ã®è©³çްã«ã€ããŠã¯ã å¥çŽã® API ãªãã¡ã¬ã³ã¹ããåç
§ãã ããã ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ã§ã¯ãæž¡ããã code_challengeãcode_challenge_method ã®ã»ãã«ãcode_verifier ãçšããŠæ€èšŒãè¡ããæ£èŠã®ãªã¯ãšã¹ãã«å¯ŸããŠã®ã¿ã¢ã¯ã»ã¹ããŒã¯ã³ãçºè¡ããŸãã å®è£
æžã¿ ããã https://github.com/aws-samples/cognito-external-idp-proxy/blob/d85fc2bed25627170fe5989b3b8d344d9b1ab860/lambda/authorize/authorize_flow.py#L66 äžèšããããã©ã³ã¶ã¯ã·ã§ã³ããšã« code_verifier ãçºè¡ããŠããããšãããããŸããmethod ã S256 ã«ãªã£ãŠããŸãã ã¢ã¯ã»ã¹ããŒã¯ã³çœ®ãæãæ»æãžã®å¯Ÿç æ»æè
ãã¢ã¯ã»ã¹ããŒã¯ã³ã眮ãæããŠå©çšè
ã«ãªãããŸãæ»æã®å¯ŸçãããŠãã ããã ID ããŒã¯ã³ã®ãã€ããŒãã«å«ãŸãã at_hash ãçšããŠãã¢ã¯ã»ã¹ããŒã¯ã³ã®çœ®ãæã察çã®ããã®æ€èšŒãè¡ã£ãŠãã ããã è©³çŽ°ãªæ€èšŒæ¹æ³ã«ã€ããŠã¯ä»¥äžããåç
§ãã ããã åèïŒãã€ããŒãéšã®æ€èšŒ ã¢ã¯ã»ã¹ããŒã¯ã³çœ®ãæãæ»æãžã®å¯Ÿç åå·Š æªå®è£
ã¯ã cognito-external-idp-proxy ã® token Lambda 颿°ã§å®è£
ããå¿
èŠããããŸãã ãã°ã®ä¿æ ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ã§ã¯ãAPI å®è¡ãã°ã®æäŸã¯è¡ããŸããã åé¡ãçºçããéã®åå ã圱é¿ç¯å²ã®èª¿æ»ãè¡ãããããAPI å®è¡ãã°ã¯ RP ã¢ããªå
ã§äžå®æéä¿åããŠãã ããã å
éšã¹ãã¬ãŒãžããã®æŒæŽ©å¯Ÿçã®ãããåŸè¿°ã®ä¿è·å¯Ÿè±¡ã®ããŒã¿ã«èšèŒãããŠããæ
å ±ã¯ããã°ã®ä¿å察象ã«ããªãã§ãã ããã ãã°ã®ä¿æ åå·Š å®è£
æžã¿ ããã APIGateway ãš Lambda ã®ãã°ã¯ååŸãããŠããŸãã ãã ããæ¢åèšå®ã¯ Amazon CloudWatch Logs ã®ãã°ã®ä¿ææéã5æ¥éã§ãããããèªç€Ÿã®ã¬ããã³ã¹ããªã·ãŒã«åãããä¿®æ£ãå¿
èŠã«ãªãå¯èœæ§ããããŸãã ãã©ãããã©ãŒã äºæ¥è
ã«å¿
èŠãšãããèšŒææžæå·å¯Ÿå¿ æ°éäºæ¥è
ããããžã¿ã«èªèšŒã¢ããªã®çœ²å API ãå©çšããå Žåãå
¬çå人èªèšŒæ³ã«åºã¥ã倧è£èªå®äºæ¥è
ïŒãã©ãããã©ãŒã äºæ¥è
ïŒãšé£æºããŠé»å眲åã®æ€èšŒåã³é»åèšŒææžã®æå¹æ§ç¢ºèªãè¡ãå¿
èŠããããŸãã é»åèšŒææžã®ä¿æåã³é»åèšŒææžã®çºè¡çªå·ã®ååŸã»ä¿æã¯ããã©ãããã©ãŒã äºæ¥è
ã«ã¯èªããããŠããŸããããã©ãããã©ãŒã äºæ¥è
ã«é»å眲åã®æ€èšŒçãå§èšããäºæ¥è
ïŒãµãŒãã¹æäŸäºæ¥è
ïŒSP äºæ¥è
ïŒã«ã¯èªããããŠããªãããã眲åAPIã®å©çšç³èŸŒã¿ãè¡ã SP äºæ¥è
ã飿ºãããã©ãããã©ãŒã äºæ¥è
ã«ä»¥äžã®å¯Ÿå¿ããé¡ãç³ãäžããŸãã æ°éäºæ¥è
ã眲å API ãå©çšããå Žåããã®å¯Ÿå¿ã¯å¿
é ãšãªããŸãã å©çšãŸã§ã®æµã SP äºæ¥è
ã¯ãäºåæºåå¥çŽã®ç³èŸŒæžã«å¯Ÿè±¡ãšãªããµãŒãã¹ãšé£æºãããã©ãããã©ãŒã äºæ¥è
ãèšå
¥ããŸããïŒèªç€Ÿããã©ãããã©ãŒã äºæ¥è
ã§ããã飿ºããäºæ¥è
ã®åœ¹å²ãå
Œããå Žåã¯èªç€Ÿåãèšå
¥ããŠãã ããããã©ãããã©ãŒã äºæ¥è
ã«é»å眲åã®æ€èšŒçãå§èšããå Žåã¯å§èšå
ã®ãã©ãããã©ãŒã äºæ¥è
åãèšå
¥ããŠãã ãããïŒ â»æå·åçšå
¬ééµã®ç»é²ã¯ãµãŒãã¹åäœã§è¡ããŸãããã¹ãç°å¢ãšæ¬çªç°å¢ã¯å¥ã®éµããçšæãã ããã ãã¹ãç°å¢ã»æ¬çªç°å¢ã®èšå®ã«ããããããžã¿ã«åºãã察象ã®ãã©ãããã©ãŒã äºæ¥è
ã«å¯ŸããŠæå·åã«çšããå
¬ééµã®ç»é²ãäŸé ŒããŸãã ãã©ãããã©ãŒã äºæ¥è
ã¯ãµãŒãã¹ããšã«éµãçæããµãŒãã¹åãšå
¬ééµãããžã¿ã«åºã«ç»é²ããŸãã ããžã¿ã«åºã¯ SP äºæ¥è
ã«å¯Ÿè±¡ãšãªããµãŒãã¹ã«ãããå
¬ééµç»é²å®äºãé£çµ¡ããŸãã æªå®è£
ããã â 眲åçšé»åèšŒææžãšçœ²åå€ã®æå·åã«ã€ã㊠ECDH-ES ã®æå·åæ¹åŒã§çœ²åçšé»åèšŒææžãšçœ²åå€ãæå·åããŠããžã¿ã«èªèšŒãµãŒããããã©ãããã©ãŒã äºæ¥è
åŽãžè¿åŽããŸãã以äžã«ããžã¿ã«èªèšŒãµãŒãã§ã®æå·ååŠçã®æŠèŠã瀺ããŸãã 眲åçšé»åèšŒææžãšçœ²åå€ãæå·åããããã®éµãã¢ïŒç§å¯éµãå
¬ééµïŒãçæããŸãã çæããç§å¯éµãšååŸãããã©ãããã©ãŒã äºæ¥è
å
¬ééµã§å
±æéµãçæããŸãã 眲åçšé»åèšŒææžãšçœ²åå€ã Base64 ãšã³ã³ãŒãããŸãã 眲åçšé»åèšŒææžãšçœ²åå€ãå
±æéµã«ããæå·åããJWE Compact Serialization 圢åŒã«ã·ãªã¢ã©ã€ãºããŸãã çæããå
¬ééµãæå·åãã眲åçšé»åèšŒææžããã³çœ²åå€ãè¿åŽããŸãã æªå®è£
ããã â ããŒã¿ã®ä¿è· ã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã°ïŒXSSïŒãã¯ãã¹ãµã€ããªã¯ãšã¹ããã©ãŒã·ãã§ãªïŒCSRFïŒãåããŠãRP ã¢ããªã®ãã£ãã·ã¥ãå
éšã¹ãã¬ãŒãžçããçŽæ¥ã¢ã¯ã»ã¹ããŒã¯ã³ãå©çšè
ã®ãã©ã€ãã·ãŒæ
å ±ãçªåãããå Žåãæ³å®ããå¿
èŠããããŸãã ã¢ã¯ã»ã¹ããŒã¯ã³ã»å©çšè
ã®ãã©ã€ãã·ãŒæ
å ±ãRPã¢ããªã§å®å
šã«ä¿æããããæå·åãè¡ããã»ãã¥ã¢ãªã¹ãã¬ãŒãžãžä¿ç®¡ããå©çšããéã«åŸ©å·ããçã®ããŒã¿æŒæŽ©å¯ŸçãããŠãã ããã ããŒã¯ã³é¢é£ private_key_jwt èªèšŒæ¹åŒã«ãããç§å¯éµ å®è£
æžã¿ ã¯ã AWS Secrets Manager ã§ä¿ç®¡ããŠããŸãã ã¢ã¯ã»ã¹ããŒã¯ã³ å®è£
æžã¿ ã¯ã Cognito ã Lambda ã®ã¡ã¢ãªã§ä¿æãããŸãã ãªãã¬ãã·ã¥ããŒã¯ã³ å®è£
æžã¿ ã¯ã Cognito ã Lambda ã®ã¡ã¢ãªã§ä¿æãããŸãã IDããŒã¯ã³ å®è£
æžã¿ ã¯ã Cognito ã Lambda ã®ã¡ã¢ãªã§ä¿æãããŸãã èªå¯ãšã³ããã€ã³ãé¢é£ èªå¯ã³ãŒã å®è£
æžã¿ ã¯ã ãµã³ãã«ãäœæãã DynmaoDB ã§ä¿ç®¡ããŸããTTL ãã€ããŠãããããæå¹æéãæ¥ããšã5ååŸã«èªåçã«åé€ãããŸããïŒ5åã¯ãµã³ãã«ã§å®çŸ©ããŠããããã©ã«ãå€ã§ããå¿
èŠã«å¿ããŠå€æŽããŠãã ãããïŒ çœ²åãã©ã³ã¶ã¯ã·ã§ã³çµæååŸãšã³ããã€ã³ãé¢é£ 眲åçšé»åèšŒææžæ
å ± æªå®è£
ããã â UserInfo ãšã³ããã€ã³ãé¢é£ å©çšè
èå¥å å®è£
æžã¿ ã¯ã Cognito ã®å±æ§ãããã³ã°ã§é£æºãããCognito ã®ãŠãŒã¶ãŒããŒã«ã§ä¿ç®¡ãããŸãã æ°å å®è£
æžã¿ ã¯ã Cognito ã®å±æ§ãããã³ã°ã§é£æºãããCognito ã®ãŠãŒã¶ãŒããŒã«ã§ä¿ç®¡ãããŸãã äœæ å®è£
æžã¿ ã¯ã Cognito ã®å±æ§ãããã³ã°ã§é£æºãããCognito ã®ãŠãŒã¶ãŒããŒã«ã§ä¿ç®¡ãããŸãã çå¹Žææ¥ å®è£
æžã¿ ã¯ã Cognito ã®å±æ§ãããã³ã°ã§é£æºãããCognito ã®ãŠãŒã¶ãŒããŒã«ã§ä¿ç®¡ãããŸãã æ§å¥ å®è£
æžã¿ ã¯ã Cognito ã®å±æ§ãããã³ã°ã§é£æºãããCognito ã®ãŠãŒã¶ãŒããŒã«ã§ä¿ç®¡ãããŸãã ããã§ã¯ã次ãããåèŠä»¶ããšã«ãã©ã®ããã«ã«ã¹ã¿ãã€ãºãã¹ãããã¿ãŠãããŸãããã æºåïŒå©çšããã©ã€ãã©ãªã®å€æŽ ãµã³ãã«ã§å©çšããŠãã python-jwt ã©ã€ãã©ãªã¯ããããéšã® kid ã®æ€èšŒã iat ã¯ã¬ãŒã ã®æ€èšŒãå®è£
ãããŠããªãããã PyJWT ã«å€æŽããŸãã倿Žããããšã«ãã£ãŠãprivate key jwt èªèšŒã®åŠçã«é¢ããéšåãä¿®æ£ããå¿
èŠããããŸããä»å㯠PyJWT ãå©çšããŸããããèªè
ã®æ¹ã
ãå®è£
ãããéã¯ããèªèº«ã®ç°å¢ã«åãããŠã©ã€ãã©ãªããéžã³ãã ããããŸããã©ã€ãã©ãªã倿Žããå Žåã¯ã Layer ã®æŽæ°æé ã«åŸãã Layer ãå¿ããã«æŽæ°ããŠãã ããã ID ããŒã¯ã³ã®æ€èšŒïŒJWT ãããéšã®æ€èšŒïŒ ID ããŒã¯ã³ã®æ€èšŒã¯ã ID ããŒã¯ã³ãååŸããåŠçãèšè¿°ãããŠãããtoken Lambda 颿°ãæŽæ°ããŸãã PyJWT ã§ã¯ãID ããŒã¯ã³çœ²åæ€èšŒçšå
¬ééµãååŸããããã®é¢æ° ãçšæãããŠãããååŸãã ID ããŒã¯ã³ã«å«ãŸãã kid ã® ID ã«æé©ãªå
¬ééµãååŸããŸããkid ã® ID ã«äžèŽããå
¬ééµãååŸããããšããããšã¯ãJWT ãããéšã®æ€èšŒã®èŠä»¶ã§ããã ãID ããŒã¯ã³ãããéšã® kid ãæ€èšŒæºåã§ååŸããå
¬ééµã® kid ãšäžèŽããããšãæ€èšŒããŠãã ãããã ãæºããããšãããããŸããå®éã®ã³ãŒãã¯ã cognito-external-idp-proxy/lambda/token/token_flow.py ã® 186 è¡ç® ã®çŽåŸã«ã以äžã®ããã«è¿œèšããŸãã print("+++ VERIFYING ID TOKEN +++") id_token = json.loads(data)["id_token"] print("+++ GET JWKS +++") jwks_client = jwt.PyJWKClient(config["jwks_uri"]) signing_key = jwks_client.get_signing_key_from_jwt(id_token) ID ããŒã¯ã³ã®æ€èšŒïŒJWT ãããéšã®æ€èšŒãšãã€ããŒãéšã®æ€èšŒïŒ ããã§ã¯ããããéšã®æ€èšŒã®èŠä»¶ã§ããã ã眲åã®ã¢ã«ãŽãªãºã ã§ãã alg ãšãæ€èšŒæºåã§ååŸããå
¬ééµã®ã¢ã«ãŽãªãºã ããšãã«ãES256ãã«ãªã£ãŠããããšãæ€èšŒããŠãã ãããã ãšã ãã€ããŒãéšã®æ€èšŒã®èŠä»¶ã§ããã ãiat ã¯ã¬ãŒã ã ã ãat_hash ã¯ã¬ãŒã ã ã®æ€èšŒãå®è£
ããŸãã ãŸããiat ã¯ã¬ãŒã ã«ã€ããŠã§ãã PyJWT ã¯ãID ããŒã¯ã³ã®ãã³ãŒã颿°ã§ iat ã¯ã¬ãŒã ã®æ€èšŒãããã©ã«ãã§è¡ããŸãããã ããèŠä»¶ã« ãæèŠæéã¯ãRP ã¢ããªã§æ±ºããå¿
èŠããããŸããã ãšããããã«ãæèŠæéããã©ã¡ãŒã¿ãšããŠæž¡ãå¿
èŠããããŸããããŒã¯ã³ãçºè¡ãããŠããæ€èšŒãè¡ããŸã§ã®æèŠæéãå®çŸ©ããã°è¯ãã®ã§ãä»å㯠60 ç§çšåºŠãšããŸããããã¯ããããã®ç°å¢ã§é©ããæéãèšå®ãã ããã å®éã®ã³ãŒãã¯ã以äžã®ããã«ãªããŸããJWT ãããéšã® kid ã®æ€èšŒã§ååŸããå
¬ééµ (signing_key) ãäžç·ã«æž¡ããŸãããã®ã³ãŒãã§ã¯ã念ã®çºãiss ã¯ã¬ãŒã ãš aud ã¯ã¬ãŒã ãæ€èšŒããããã«ãå¿
èŠãªãã©ã¡ãŒã¿ãæž¡ããŠããŸãã cognito-external-idp-proxy/lambda/token/token_flow.py ã® signing_key = jwks_client.get_signing_key_from_jwt(id_token) ã®çŽåŸã«ä»¥äžãèšè¿°ããŸãã try: decoded_id_token = jwt.decode( id_token, signing_key, # kid ã®æ€èšŒæã«ååŸããå
¬ééµ leeway=60, # æèŠæéã« 60 ç§ãèšå® issuer=config["idp_issuer_url"], # iss ã¯ã¬ãŒã ã®æ€èšŒ audience=config["client_id"], # aud ã¯ã¬ãŒã ã®æ€èšŒ algorithms=["ES256"]) ç¶ããŠãat_hash ã¯ã¬ãŒã ã®æ€èšŒãå®è£
ããŸãã at_hash ã¯ã¬ãŒã ã¯ãèŠä»¶ã«ããéãã ãã¢ã¯ã»ã¹ããŒã¯ã³ããID ããŒã¯ã³ãããéšã® algorithm ã¯ã¬ãŒã ïŒalgïŒãšåãããã·ã¥ã¢ã«ãŽãªãºã ãçšããŠããã·ã¥åããããã·ã¥åããçµæã®å·Šååã®ããã矀ã Base64URL ã«ãŠãšã³ã³ãŒãããå€ã ã§ãã ç¹°ãè¿ãã«ãªããŸããããŸããïŒâ ïŒããŒã¯ã³ãšã³ããã€ã³ãããååŸããããŒã¿ãããã¢ã¯ã»ã¹ããŒã¯ã³ãååŸã»æ€èšŒããïŒâ¡ïŒsha256 ã§ããã·ã¥åããŸããç¶ããŠãïŒâ¢ïŒããã·ã¥åããå€ã®å·Šååã®ããã矀ã Base64URL ã§ãšã³ã³ãŒãããat_hash ãçæããŸããæåŸã«ãïŒâ£ïŒçæãã at_hash ãšãã³ãŒããã ID ããŒã¯ã³ã«å«ãŸãã at_hash ãæ¯èŒããåãå€ã§ããããšã確èªããŸãã cognito-external-idp-proxy/lambda/token/token_flow.py ã® äžèšãã³ãŒãåŠçã®çŽåŸã«ä»¥äžãèšè¿°ããŸãã access_token = json.loads(data)["access_token"] # â ã¢ã¯ã»ã¹ããŒã¯ã³ã®ååŸ jwt.decode( access_token, signing_key, algorithms=["ES256"]) # ã¢ã¯ã»ã¹ããŒã¯ã³ã®æ€èšŒ hashed_access_token = hashlib.sha256(access_token.encode()).digest() # â¡ calculated_at_hash = base64.urlsafe_b64encode(hashed_access_token[:len(hashed_access_token)//2]).decode('utf-8').rstrip('=') # ⢠# ⣠if decoded_id_token['at_hash'] != calculated_at_hash: print("at_hash mismatch") return { "statusCode": 400 } ãªãã¬ã€æ»æãžã®å¯Ÿç èŠä»¶ã§ã¯ããªãã¬ã€æ»æãžã®å¯Ÿçãšã㊠nonce ã®å©çšãæ±ããããŠããŸãã ä»åã¯ãauthorize Lambda 颿°ã§ãªã¯ãšã¹ãããšã« nonce ãçæããèªå¯ãªã¯ãšã¹ãã«èšå®ããDynamoDB ã® state ããŒãã«ãš code ããŒãã«ã§ nonce ãäžå®æéä¿æããtoken Lambda 颿°ã§æ€èšŒããŸãã ãŸããauthorize Lambda 颿°ã§ nonce ãçæããåŠçã®å®è£
ã§ããããã§ã¯ãSecrets Manager ã®ã¯ã©ã€ã¢ã³ããçšããŠãã©ã³ãã ãã¹ã¯ãŒããçæãããã®ä¹±æ°ã nonce ã®ãã©ãŒãããã«åãããã«å€æããŸãã 以äžã®ã³ãŒãã cognito-external-idp-proxy/lambda/authorize/authorize_flow.py ã® 77 è¡ç® ãããã«è¿œå ããŸãã nonce_random = SM_CLIENT.get_random_password( PasswordLength=64, ExcludePunctuation=True, IncludeSpace=False ) nonce_random = nonce_random["RandomPassword"] nonce = hashlib.sha256(nonce_random.encode("utf-8")).digest() nonce = base64.urlsafe_b64encode(nonce).decode('utf-8') nonce = nonce.replace('=', '') RP ã§çæãã nonce ã state ããŒãã«ã§ç®¡çãããããstate ã code_verifier ãæ ŒçŽããåŠçã§åãã㊠state ããŒãã«ã«æ ŒçŽããããã«ã cognito-external-idp-proxy/lambda/authorize/authorize_flow.py ã® 82 è¡ç®ã®ã³ãŒã ãæžãæããŸãã DYNAMODB_CLIENT.put_item( TableName = config["state_table"], Item = { "state": { "S": str(hashed_state) }, "code_verifier": { "S": code_verifier }, "ttl": { "N": str(state_ttl) }, # Add "nonce": { "S": nonce } } ) ãããŠãããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ã®èªå¯ãšã³ããã€ã³ãã®ãã©ã¡ãŒã¿ãšããŠèšå®ããŸãã ããã§ã¯ãcognito-external-idp-proxy ã®å®è£
ã«ã¯ååšããªããã©ã¡ãŒã¿ã§ããã acr_values ãåãããŠèšå®ããŸããïŒåç
§ïŒèªå¯ãšã³ããã€ã³ãã®ãªã¯ãšã¹ããã©ã¡ãŒã¿ã https://developers.digital.go.jp/documents/auth-and-sign/authserver/ ïŒ cognito-external-idp-proxy/lambda/authorize/authorize_flow.py ã® 100 è¡ç®ããã ã«ä»¥äžã®ã³ãŒãã远å ããŸãã params["nonce"] = nonce params["acr_values"] = "aal3 crl" ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ããã®ã¬ã¹ãã³ã¹ã«ãnonce ã¯å«ãŸããŠããŠããstate ã¯å«ãŸããŠããŸããããã®ãŸãŸã§ã¯ state ããŒãã«ãããRP ãçæãã nonce ãååŸã§ãããã¬ã¹ãã³ã¹ã«å«ãŸãã nonce ãæ€èšŒã§ããŸãããããã§ãcallback Lambda 颿°ã§ãstate ããŒãã«ã® nonce ããcode ããŒãã«ã«æ ŒçŽããtoken Lambda 颿°ã§ãcode ãããŒã«ãnonce ãååŸããããã cognito-external-idp-proxy/lambda/callback/callback_flow.py ã® 81 è¡ç® ã® code ããŒãã«ãžã®è¿œå åŠçã以äžã®ããã«å€æŽããŸãã DYNAMODB_CLIENT.put_item( TableName = config["auth_code_table"], Item = { "auth_code": { "S": config["auth_code"] }, "code_verifier": { "S": code_verifier }, # Add "nonce": { "S": state_result["Item"]["nonce"]["S"] }, "ttl": { "N": str(code_ttl) } } ) ããã§ RP ãçæãã nonce ã token Lambda 颿°ã§ååŸããæºåãæŽããŸããã æåŸã«ã cognito-external-idp-proxy/lambda/token/token_flow.py ã® 122 è¡ç® ã®çŽåŸã«ã以äžã®ã³ãŒãã远å ããcode ããŒãã«ããååŸãã nonce ã倿°ã«æ ŒçŽããŸãã nonce = code_result["Item"]["nonce"]["S"] ãã³ãŒããã ID ããŒã¯ã³ã®æ€èšŒåŸïŒ if decoded_id_token['at_hash'] != calculated_at_hash: ã§ã®æ€èšŒåŸïŒã«ã以äžã®ã³ãŒãã远å ãã倿°ã«æ ŒçŽãã nonce ã®å€ïŒ RP ã§çæãããã®ïŒãš ID ããŒã¯ã³ã«å«ãŸãã nonce ã®å€ïŒããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ã®ã¬ã¹ãã³ã¹ïŒãçããããæ€èšŒããŸãã if decoded_id_token["nonce"] != nonce: print("Nonce mismatch") return { "statusCode": 400 } # ããŒã¯ã³ã®æ€èšŒæã«äŸå€ãçºçããããèªèšŒå€±æãšããããã®äŸå€åŠç except Exception as e: return { "statusCode": 400 } ã¢ã¯ã»ã¹ããŒã¯ã³çœ®ãæãæ»æãžã®å¯Ÿç ãã®èŠä»¶ã¯ãID ããŒã¯ã³ã®ãã€ããŒãæ€èšŒæã«ã at_hash ã¯ã¬ãŒã ã®æ€èšŒãè¡ãªã£ãŠãããããå¯Ÿçæžã¿ãšãªããŸãã SPA ãããã¯ãšã³ãã§å¿
èŠãªå®è£
ãããŸã§ã®å®è£
ã§ãããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãéããŠãèªèšŒãè¡ãããšãã§ããããã«ãªããŸãããå®éã®ãµãŒãã¹ã§ã¯ããã®èªèšŒåŸã«çºè¡ããã Cognito ã®ããŒã¯ã³ãçšããŠããµãŒãã¹ã® API ã«å¯Ÿããèªå¯åŠçãè¡ããããŸããŸãªåŠçãå®è¡ããããšã«ãªããŸãã ä»åã®ãã¢ã§ã¯ãCognito ã®ãŠãŒã¶å±æ§ããçå¹Žææ¥ã確èªãã20æ³ä»¥äžãã©ãã倿ãããšããäžé£ã®æµãããReact ãš Amazon API Gateway ãš COGNITO_USER_POOLS ã¿ã€ãã®ãªãŒãœã©ã€ã¶ãŒïŒãªãŒãœã©ã€ã¶ãŒïŒãå©çšããŠå®è£
ããããšæããŸããReact ãã㯠Cognito ã®ã¢ã¯ã»ã¹ããŒã¯ã³ãéä¿¡ãããªãŒãœã©ã€ã¶ãŒã§ã¢ã¯ã»ã¹ããŒã¯ã³ã«å«ãŸããã¹ã³ãŒãïŒ custom/read:age )ãæ€èšŒããAPI ã®å©çšå¯åŠã倿ïŒèªå¯ïŒããŸãã ãã®ã¢ã¯ã»ã¹ããŒã¯ã³ã«å«ãŸããã¹ã³ãŒãã¯ãç¬èªã§ Cognito ã«èšå®ããããã¢çšã«äœæãã API ã®ããã®ã¹ã³ãŒãã§ããå
·äœãªå®è£
ã¯ããã®åŸã® ãCDK ã®å®è£
äŸã ã§ç€ºããŠããŸãã SPA ã®å®è£
ä»åã¯ãCognito ã®ã³ã³ãœãŒã«ã§ç޹ä»ãããŠãããµã³ãã«å®è£
ãå©çšããŸããå©çšããã©ã€ãã©ãªã¯ã oidc-client-ts ã©ã€ãã©ãªãš react-oidc-context ã©ã€ãã©ãªã§ããSPA ã«ããã OIDC ã®å©çšã§è«ç¹ã«ãªãã®ã¯ãããŒã¯ã³ã®ãã³ããªã³ã°æ¹æ³ã§ãããããã¯åãµãŒãã¹ã®ããªã·ãŒã«åãããBFF ããCustom Storage ãªã©ã®æ¡çšããæ€èšãã ããã æ¬å®è£
ã¯ãããŸã§ãµã³ãã«ã§ãã InMemoryWebStorage ãå©çšããããšã§ããŒãžãªããŒãæã®ãŠãŒã¶äœéšãæãªãããŠããŸãç¹ããç°å¢å€æ°ãããã¯ãšã³ããšã®æ¥ç¶èšå®ããAPI åŒã³åºãã®åŠçãªã©ãçæ§ããå©çšãããéã«ã¯ãæ¬çªç°å¢åãã«ãé©åã«å®è£
ããŠãã ããã å®è£
äŸ â main.tsx import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import { AuthProvider } from "react-oidc-context"; import "./index.css"; import { InMemoryWebStorage, WebStorageStateStore } from "oidc-client-ts"; const cognitoAuthConfig = { authority: "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXXX", client_id: "xxxxxxxxxxxxxxxxxx", redirect_uri: "http://localhost:5173", // ãã¢ã¢ããªã®ãããlocalhostã§å®è£
ããŠããŸãã response_type: "code", scope: "custom/read:age openid", automaticSilentRenew: true, silentRedirectUri: "http://localhost:5173", // ãã¢ã¢ããªã®ãããlocalhostã§å®è£
ããŠããŸãã onSigninCallback: () => { window.history.replaceState({}, document.title, window.location.pathname); }, userStore: new WebStorageStateStore({ store: new InMemoryWebStorage() }), }; const root = ReactDOM.createRoot(document.getElementById("root")!); root.render( <React.StrictMode> <AuthProvider {...cognitoAuthConfig}> <App /> </AuthProvider> </React.StrictMode> ); å®è£
äŸ â App.tsx ä»åã¯ã RFC6750 ã«åŸããã¢ã¯ã»ã¹ããŒã¯ã³ãæå®ãããªãŒãœã©ã€ã¶ãŒã§ã¹ã³ãŒãã«ããã¢ã¯ã»ã¹å¶åŸ¡ãå®çŸããŸãã åèïŒ ãªãœãŒã¹ãµãŒããŒã䜿çšããã¹ã³ãŒããM2Mãããã³ API import { useCallback, useEffect, useState } from "react"; import { useAuth } from "react-oidc-context"; import AppIcon from "../public/App-Icon_White.svg"; function App() { const auth = useAuth(); const [isLoading, setIsLoading] = useState(true); const [isOver20, setIsOver20] = useState(false); const signOutRedirect = () => { const clientId = "xxxxxxxxxxxxxxxxxxxxxxxx"; const logoutUri = "http://localhost:5173"; const cognitoDomain = "https://xxxxxxxxxx.auth.ap-northeast-1.amazoncognito.com"; window.location.href = `${cognitoDomain}/logout?client_id=${clientId}&logout_uri=${encodeURIComponent(logoutUri)}`; }; const ageCheck = useCallback(async() => { try { const res = await fetch("https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/age-check", { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${auth.user?.access_token}`, // ã¢ã¯ã»ã¹ããŒã¯ã³ãæå®ãã }, }); if (res.ok) { const data = await res.json(); setIsOver20(Boolean(data.isOver20)); setIsLoading(false); } } catch(error) { console.error("Error was happend:", error); } }, [auth.user?.access_token]) useEffect(() => { if (auth.isAuthenticated) { ageCheck(); } }, [auth.isAuthenticated, ageCheck]) if (auth.isLoading) { return <div>Loading...</div>; } if (auth.error) { return <div>Encountering error... {auth.error.message}</div>; } return ( <div className="flex flex-col items-center justify-center h-screen"> <h1 className="text-2xl mb-20">ããžã¿ã«èªèšŒã¢ã㪠ã㢠on AWS</h1> <> {auth.isAuthenticated ? ( <> <pre> {auth.user?.profile.name} ããã¯ã {isLoading ? "èªã¿èŸŒã¿äž..." : isOver20 ? "20æ³ä»¥äžã§ãã" : "20æ³æªæºã§ãã"} </pre> <button className="mt-4 flex justify-center items-center bg-digital-agency-blue-default text-white px-4 py-2 rounded-md hover:bg-digital-agency-blue-hover active:bg-digital-agency-blue-active focus:outline-none focus:ring-2 focus:ring-[#0017C1] focus:ring-opacity-50" onClick={() => signOutRedirect()}> ãã°ã¢ãŠã </button> </> ) : ( <> <div className="text-center">æ¬äººç¢ºèªã®ãããããžã¿ã«åºãæäŸããããžã¿ã«èªèšŒã¢ããªãå©çšããŸãã</div> <button className="mt-4 flex justify-center items-center bg-digital-agency-blue-default text-white px-4 py-2 rounded-md hover:bg-digital-agency-blue-hover active:bg-digital-agency-blue-active focus:outline-none focus:ring-2 focus:ring-[#0017C1] focus:ring-opacity-50" onClick={() => auth.signinRedirect()}> <img src={AppIcon} alt="App Icon" className="mr-4 w-6 h-6 fill-white" /> ãã€ãã³ããŒã«ãŒãã§ãã°ã€ã³ </button> </> )} </> <footer className="absolute right-0 left-0 bottom-0 bg-gray-100 w-full"> <div className="flex justify-center space-x-4"> <pre>ãµãŒãã¹ã«ã€ããŠ</pre> <pre>å©çšèŠçŽ</pre> <pre>ããªã·ãŒ</pre> </div> </footer> </div> ); } export default App ã¢ããªã®ãã¶ã€ã³ã«ã€ã㊠ããžã¿ã«èªèšŒã¢ããªãå©çšããã¢ããªã±ãŒã·ã§ã³ã«ã¯ããã¶ã€ã³ã¬ã€ãã©ã€ã³ã瀺ãããŠããŸããããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ã¯ããã¡ãã«åŸãããã«ããŠãã ããã https://developers.digital.go.jp/documents/auth-and-sign/design-guideline/ ããã¯ãšã³ãã®å®è£
åè¿°ã®éããAPI Gateway ãš ãªãŒãœã©ã€ã¶ãŒãLambda ã§ API ãšãã®èªå¯åŠçãå®è£
ããŸããä»åå®è£
ãããµã³ãã«ã®ããã¯ãšã³ãã§ã¯ãããŒã¯ã³ããŒã¹ã§ã¢ã¯ã»ã¹å¶åŸ¡ãã€ãŸãããŒã¯ã³ææè
ã«ãã代çã¢ã¯ã»ã¹ãšããŠãããã¯ãšã³ããä¿æãããŠãŒã¶ãŒæ
å ±ãç¹å®ãç
§åããããšã§ãããŒã¿ã«ã¢ã¯ã»ã¹ããŠãããŠãŒã¶ãŒããæ¬åœã«ãã®ããŒã¿ã®ãŠãŒã¶ãŒã§ããããæ€èšŒããŠããŸãã CDK ã®å®è£
äŸ ããã¯ãšã³ãçšã®ã³ã³ã¹ãã©ã¯ããæ¬¡ã®ããã«äœæããŸãããã®ã³ã³ã¹ãã©ã¯ãã PjwtWithPkceStack ããåŒã³åºããŠãã ããã construct ã®å®è£
äŸïŒãã¡ã€ã«ã¯ã cognito-external-idp-proxy/cdk/construct/backend.py ã®ããã«è¿œå ããŸãïŒã¯æ¬¡ã®ããã«ãªããŸãã from aws_cdk import ( aws_apigateway as _apigw, aws_lambda as _lambda, aws_cognito as _cognito, aws_iam as _iam, Duration, ) from constructs import Construct from cdk_nag import NagSuppressions class Backend(Construct): def __init__(self, scope: Construct, id: str, user_pool: _cognito.UserPool, oauth_scopes: list[str], **kwargs): super().__init__(scope, id) # Create API Gateway with CORS self.api = _apigw.RestApi( self, "BackendApi", rest_api_name="Backend API", description="API Gateway with COGNITO_USER_POOLS type authorizer and custom scopes", default_cors_preflight_options=_apigw.CorsOptions( allow_origins=["http://localhost:5173"], allow_methods=["GET", "OPTIONS"], allow_headers=["Content-Type", "X-Amz-Date", "Authorization", "X-Api-Key", "X-Amz-Security-Token", "X-Amz-User-Agent"], max_age=Duration.days(1) ) ) # Create an authorizer of the type of COGNITO_USER_POOLS auth = _apigw.CognitoUserPoolsAuthorizer( self, "CognitoUserPoolsAuthorizer", cognito_user_pools=[user_pool] ) # Create age_check Lambda function age_check_lambda = _lambda.Function( self, "AgeCheckFunction", runtime=_lambda.Runtime.PYTHON_3_10, handler="age_check.handler", code=_lambda.Code.from_asset("./lambda/age_check"), timeout=Duration.seconds(30), environment={ "USER_POOL_ID": user_pool.user_pool_id } ) age_check_lambda.add_to_role_policy( _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=["cognito-idp:AdminGetUser"], resources=[user_pool.user_pool_arn] ) ) # Create API resources and methods api_resource = self.api.root.add_resource("api") age_check_resource = api_resource.add_resource("age-check") # Add method with authorization age_check_get_method = age_check_resource.add_method( "GET", _apigw.LambdaIntegration(age_check_lambda), authorizer=auth, authorization_type=_apigw.AuthorizationType.COGNITO, authorization_scopes=["custom/read:age"] # Custom scope for age check ) # Grant Lambda permissions to be invoked by API Gateway age_check_lambda.add_permission( "ApiGatewayInvoke", principal=_iam.ServicePrincipal("apigateway.amazonaws.com"), source_arn=age_check_get_method.method_arn ) # Create custom resource scope age_check_scope = _cognito.ResourceServerScope( scope_name="read:age", scope_description="Permission to access age check endpoint" ) # Create resource server for custom scopes resource_server = user_pool.add_resource_server( "ResourceServer", identifier="custom", scopes=[age_check_scope] ) # Add custom scope to OAuth scopes oauth_scopes.append(_cognito.OAuthScope.resource_server(resource_server, age_check_scope)) """ SUPPRESSION RULES FOR CDK_NAG """ NagSuppressions.add_resource_suppressions( self.api, [ { "id": "AwsSolutions-APIG2", "reason": "This is demo api. There is no request parameters for this API."} ] ) NagSuppressions.add_resource_suppressions( self.api.deployment_stage, [ { "id": "AwsSolutions-APIG1", "reason": "This is demo api. Logging of APIGW is not necessary. Instead, Lambda turned on logging."}, { "id": "AwsSolutions-APIG3", "reason": "This is demo api. This is protected by COGNITO_USER_POOLS authorizer. It's enough for dev env. If you go to production, you have to attach WAF."}, { "id": "AwsSolutions-APIG6", "reason": "This is demo api. Logging of APIGW is not necessary. Instead, Lambda turned on logging."} ] ) NagSuppressions.add_resource_suppressions( age_check_lambda.role, [ { "id": "AwsSolutions-IAM4", "reason": "Managed policies were used by CDK automatically. Managed policies make CDK simple."} ] ) NagSuppressions.add_resource_suppressions( age_check_lambda, [ { "id": "AwsSolutions-L1", "reason": "Runtime version followed cognito-external-idp-proxy's runtime version."} ] ) Lambda ã®å®è£
äŸ import boto3 import json from datetime import datetime import os headers = { 'Access-Control-Allow-Origin': 'http://localhost:5173', 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Allow-Methods': 'OPTIONS,GET' } def get_birthdate(username): """ Get custom:birth attribute by cognito AdminGetUser api """ try: cognito = boto3.client('cognito-idp') response = cognito.admin_get_user( UserPoolId = os.environ.get('USER_POOL_ID'), Username = username ) # Get value when match UserAttributes Name is custom:birth birthdate:str = next((attr['Value'] for attr in response['UserAttributes'] if attr['Name'] == 'custom:birth'), None) return birthdate except Exception as e: raise ValueError(f"Error getting birthdate: {str(e)}") def calculate_age(birthdate_str): """ Calculate age from birthdate string in YYYYMMDD format """ try: birthdate = datetime.strptime(birthdate_str, '%Y%m%d') today = datetime.now() age = today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day)) return age except ValueError as e: raise ValueError(f"Invalid birthdate format. Expected YYYY-MM-DD, got: {birthdate_str}") def handler(event, context): """ Lambda function to check age from claims in the JWT token and verify if user is over 20 """ try: print("+++ event +++") print(event) # Get the claims from the authorizer context claims = event['requestContext']['authorizer']['claims'] # Check if sub claim exists if 'username' not in claims: return { 'statusCode': 400, 'headers': headers, 'body': json.dumps({ 'message': 'sub claim not found in token' }) } # Calculate age and check if over 20 birthdate = get_birthdate(claims['username']) if birthdate is None: return { 'statusCode': 400, 'headers': headers, 'body': json.dumps({ 'message': 'This user is not found in userpool.' }) } age = calculate_age(birthdate) is_over_20 = age >= 20 return { 'statusCode': 200, 'headers': headers, 'body': json.dumps({ 'isOver20': is_over_20, }) } except Exception as e: return { 'statusCode': 500, 'headers': headers, 'body': json.dumps({ 'message': f'Error processing request: {str(e)}' }) } 屿§ãããã³ã°ã®ä¿®æ£ èªèšŒåŸã«ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ããååŸã§ããåžé¢æ
å ±ïŒåºæ¬4æ
å ±ïŒããããã³ã°ãããå Žåã name , gender , address 㯠Cognito ã®æšæºã®å±æ§ãå©çšã§ããŸããã birthdate 㯠10 æ¡ã® YYYY-mm-dd ãã©ãŒãããã§ããå¿
èŠãããããããã® API ã§æäŸãããããŒã¿åœ¢åŒãšç°ãªãããšã©ãŒãšãªã£ãŠããŸããŸããããã§ãUserPool äœææã«ãã«ã¹ã¿ã 屿§ãšã㊠custom:birthdate ã远å ãããã°ã€ã³æã«ãã®ã«ã¹ã¿ã 屿§ã«åžé¢æ
å ±ã®çå¹Žææ¥ã飿ºããŸãããããè¡ãã«ã¯ã以äžã®ããã« CDK ã®ãœãŒã¹ã³ãŒããä¿®æ£ããŸãã UserPool ãžã®ã«ã¹ã¿ã 屿§ã®è¿œå çå¹Žææ¥ããããã³ã°ãããå Žåã ãã远å ããŠãã ããã cognito-external-idp-proxy/cdk/pjwt_with_pkce_stack.py ã® 346è¡ç® ã以äžã®ããã«æŽæ°ããŠãã ããã # Cognito User Pool base configuration cognito_user_pool = _cognito.UserPool(self, "UserPool", custom_attributes={"birth": _cognito.StringAttribute(mutable=True, max_len=8, min_len=8)} ) ã«ã¹ã¿ã 屿§ã®ãããã³ã° ãŸããåºæ¬ 4 æ
å ±ãå
šãŠãããã³ã°ããå Žåã¯ã以äžã®ããã«ä¿®æ£ããŠãã ãããïŒäžèŠãªå±æ§ã¯ãããã³ã°å¯Ÿè±¡ããé€å€ããŠãã ãããïŒ cognito-external-idp-proxy/cdk/pjwt_with_pkce_stack.py ã® 350è¡ç® ã以äžã®ããã«æŽæ°ããŠãã ããã cognito_user_pool_idp_oidc = _cognito.UserPoolIdentityProviderOidc( self, "IdentityProvider", client_id = self.node.try_get_context("idp_client_id"), client_secret = self.node.try_get_context("idp_client_secret"), issuer_url = idp_issuer_url, user_pool = cognito_user_pool, attribute_request_method = _cognito.OidcAttributeRequestMethod.GET, attribute_mapping = _cognito.AttributeMapping( address = _cognito.ProviderAttribute.other("address"), fullname = _cognito.ProviderAttribute.other('name'), gender = _cognito.ProviderAttribute.other("gender"), custom = { "custom:birth": _cognito.ProviderAttribute.other("birthdate"), } ), endpoints = _cognito.OidcEndpoints( authorization = apigw_proxy_route_auth_uri, jwks_uri = idp_issuer_url + self.node.try_get_context("idp_keys_path"), token = apigw_proxy_route_token_uri, user_info = idp_issuer_url + self.node.try_get_context("idp_attributes_path") ), name = self.node.try_get_context("idp_name"), scopes = self.node.try_get_context("idp_scopes").split() ) Tips Cognito ã¯ã«ã¹ã¿ã 屿§ãããŒã«æ€çŽ¢ã§ããªããããã«ã¹ã¿ã 屿§ãããŒã«æ€çŽ¢ãããèŠä»¶ãããã°ãå€éšã®ããŒã¿ã¹ãã¢ïŒAmazon DynamoDB ãªã©ïŒã«åžé¢æ
å ±ãä¿åããããšããæ€èšãã ããã ãããã€æ¹æ³ ãããŸã§å®è£
ãç²ãæ§ã§ããããã®åŸã¯ãããã€ãè¡ããåäœã確èªããŠãããŸãã cognito-external-idp-proxy ã®ã«ã¹ã¿ãã€ãºãå®äºããããžã¿ã«èªèšŒã¢ããªã®ãµã³ãããã¯ã¹ç°å¢ã®æºåãæŽã£ãŠããããšã確èªãã以äžã®ããã«ãã©ã¡ãŒã¿ãèšå®ã®äžãcognito-external-idp-proxy ã® README ã®æé ã«æ²¿ã£ãŠãããã€ããŸãã { "api_version": "v1", "api_authn_route": "/oauth2/authorize", "api_callback_route": "/callback", "api_token_route": "/oauth2/token", "idp_attributes_path": "/protocol/openid-connect/userinfo", "idp_auth_path": "/protocol/openid-connect/auth", "idp_keys_path": "/protocol/openid-connect/certs", "idp_token_path": "/api/realms/main/protocol/openid-connect/token", "idp_client_id": "xxxxxxxxxxxxxxxxxxxxxxxx", "idp_client_secret": "secret", "idp_issuer_url": "https://sb-auth-and-sign.go.jp/api/realms/main", "idp_name": "digital-auth-and-sign", "idp_scopes": "openid name address birthdate gender", "pkce": true, "stack_name": "CognitoExternalIdpProxyStack", "userpool_allowed_callback_url": "http://localhost:5173" } ç¡äºãããã€ãçµããã°ãJ-LIS ãã貞äžãããããŸãã¯è³Œå
¥ãããã¹ãã«ãŒããå©çšããåäœç¢ºèªãè¡ããŸãã åäœç¢ºèª ããã§ã¯ãããžã¿ã«èªèšŒã¢ããªã§ã®ãã°ã€ã³ãšåžé¢æ
å ±ïŒãŠãŒã¶ã®æ
å ±ãåºæ¬ïŒæ
å ±ïŒãåããããååŸããã¢ã¯ã»ã¹ããŒã¯ã³ãçšããŠå¹Žéœ¢ç¢ºèª API ã«ãªã¯ãšã¹ããéãã20 æ³ä»¥äžãã©ããå€å®ãããã¢ããåç»ã§ã芧ãã ããã SPA ã«è¡šç€ºãããŠããã ããã€ãã³ããŒã«ãŒãã§ãã°ã€ã³ã ãã¯ãªãã¯ãããšãããžã¿ã«èªèšŒã¢ããªãæäŸãããµãŒãã¹ããŒãžã«é·ç§»ããããžã¿ã«èªèšŒã¢ããªã®ãã°ã€ã³ã·ãŒã±ã³ã¹ãå§ãŸããŸãã QR ã³ãŒãã衚瀺ãããŸãã®ã§ããããã¹ããŒããã©ã³ã§èªã¿åããšãã¹ããŒããã©ã³ã«ã€ã³ã¹ããŒã«ãããããžã¿ã«èªèšŒã¢ããªãèµ·åãããã€ãã³ããŒã«ãŒãã«ãããã°ã€ã³ãæ±ããããŸãã ãã°ã€ã³ãæåãããšãå
ã»ã©ãŸã§ããžã¿ã«èªèšŒã¢ããªã®ãµãŒãã¹ããŒãžã衚瀺ãããŠããã¿ãã¯ããªãã€ã¬ã¯ãã SPA ã®ãã°ã€ã³åŸç»é¢ã«é·ç§»ããŸããåžé¢æ
å ±ã¯ãCognito ã®å±æ§ãããã³ã°ã«ãããCognito ãžé£æºãããŠãããããCognito ã® API ããååŸã§ããSPA äžã«è¡šç€ºããããšãã§ããŸãã Cognito ãžå±æ§ãããã³ã°ãããåžé¢æ
å ±ã¯ãåŒãç¶ããèªç€Ÿã®ãµãŒãã¹ã§å©çšããããšãã§ããŸããä»åã¯ãã¢ã¯ã»ã¹ããŒã¯ã³ã® sub ã¯ã¬ãŒã ããååŸãããŠãŒã¶ãŒ ID ãããŒã«ãCognito ã«ç»é²ãããçå¹Žææ¥ã GetAdminUser API ã§ååŸãããã°ã€ã³ãããŠãŒã¶ãŒã 20 æ³ä»¥äžãå€å®ããåŠçãå®è£
ããŸããã ãã®ããã«ããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãå©çšããèªç€Ÿã®ãµãŒãã¹ãããé«åºŠã«ã»é«äŸ¡å€ã«ããŠããããšãã§ããããšã§ãããã çµããã« ãããã§ããã§ãããããæ¬èšäºã§ã¯ãããžã¿ã«èªèšŒã¢ããªãµãŒãã¹ API ãå©çšããCognito ãšé£æºããããã®å®è£
æ¹æ³ã玹ä»ããŸããã çæ§ãå®è£
ãè¡ãéã«ã¯ãå©çšäºå®ã®ãŠãŒã¶ã®èªèšŒãªã¯ãšã¹ãã®äºæž¬æ°ãããAWS åŽã® API ã®ã¯ã©ãŒã¿ïŒæ¬ãµã³ãã«ã§ã¯ãCognito ã® GetAdminUser API ããSecrets Manager ã® GetRandomPassword API ãå©çšããŸãïŒã確èªããåé¡ãªããµãŒãã¹æäŸã§ããããèšèšãã ããã æ¬äººèªèšŒãæ¬äººç¢ºèªã¯ããµãŒãã¹ãæäŸããäžã§ãªããŠã¯ãªããªããã®ã§ãããäžé£ã®äœéšã¯ãã客æ§ã®æºè¶³åºŠåäžã«å€§ããå¯äžããéšåã§ããJPKI ãå©çšããèªèšŒã«ããããµãŒãã¹ã®å©äŸ¿æ§ãåäžãããããšã¯ãã®ããžã¿ã«ç€ŸäŒã§ã¯äžå¯æ¬ ã§ãã®ã§ããã²ã掻çšããã ããã°ãšæããŸãã èè
éŽæš éœäž (Yozo Suzuki) ã¢ããŸã³ ãŠã§ã ãµãŒãã¹ ãžã£ãã³ååäŒç€Ÿ ãããã¿ã€ã ãœãªã¥ãŒã·ã§ã³ ã¢ãŒããã¯ã 奜ããªAWSã®ãµãŒãã¹ã¯ãAWS CDK ãš AWS IAM Identity Center è¶£å³ã¯ããã³ã¬ãèªãããšãšãã«ã¡ã©ã§åšã®åçãæ®ãããš