TypeScriptã®éçåä»ããæŽ»çšããå ç¢ã§å®å šãªããžã¿ã«çœ²åã·ã¹ãã ãæ§ç¯ããæ¹æ³ã詳説ãåå®å šãªãã¿ãŒã³ã§è匱æ§ãé²ããèªèšŒã匷åããææ³ãåŠã³ãŸãã
TypeScriptããžã¿ã«çœ²åïŒèªèšŒã®åå®å šæ§ãå®çŸããå æ¬çã¬ã€ã
è¶ æ¥ç¶ç€ŸäŒãšãªã£ãçŸä»£ã®ã°ããŒãã«çµæžã«ãããŠãããžã¿ã«ã®ä¿¡é Œã¯ç©¶æ¥µã®é貚ã§ããéèååŒããå®å šãªéä¿¡ãæ³çã«æå¹ãªå¥çŽã«è³ããŸã§ãæ€èšŒå¯èœã§æ¹ããäžå¯èœãªããžã¿ã«ã¢ã€ãã³ãã£ãã£ã®å¿ èŠæ§ã¯ããã€ãŠãªãã»ã©é«ãŸã£ãŠããŸãããã®ããžã¿ã«ã®ä¿¡é Œã®äžå¿ã«ããã®ãããžã¿ã«çœ²åã§ããããã¯èªèšŒãå®å šæ§ãåŠèªé²æ¢ãæäŸããæå·æè¡ã®é©ç°ã§ãããããããããã®è€éãªæå·ããªããã£ãã®å®è£ ã«ã¯å±éºã䌎ããŸãããã£ãäžã€ã®å€æ°ã®çœ®ãééããäžæ£ãªããŒã¿åããããã¯äºçްãªããžãã¯ãšã©ãŒããã»ãã¥ãªãã£ã¢ãã«å šäœãéãã«èã¿ã壿» çãªè匱æ§ãçã¿åºãå¯èœæ§ããããŸãã
JavaScriptãšã³ã·ã¹ãã ã§åãéçºè ã«ãšã£ãŠããã®èª²é¡ã¯ããã«å¢å¹ ãããŸãããã®èšèªã®åçã§ç·©ããã«åä»ããããæ§è³ªã¯ãé©ãã¹ãæè»æ§ãæäŸããäžæ¹ã§ãã»ãã¥ãªãã£ã®æèã§ã¯ç¹ã«å±éºãªã¯ã©ã¹ã®ãã°ãžã®æãéããŸããæ©å¯æ§ã®é«ãæå·éµãããŒã¿ãããã¡ãããåãããéãåçŽãªåã®åŒ·å¶ããå®å šãªçœ²åãšç¡äŸ¡å€ãªçœ²åã®åããç®ã«ãªãããšããããŸããããã§TypeScriptããåãªãéçºè ã®å©äŸ¿æ§ããŒã«ãšããŠã§ã¯ãªããæ¥µããŠéèŠãªã»ãã¥ãªãã£ããŒã«ãšããŠç»å Žããã®ã§ãã
ãã®å æ¬çãªã¬ã€ãã§ã¯ãèªèšŒã®åå®å šæ§ãšããæŠå¿µãæ¢æ±ããŸããTypeScriptã®éçåã·ã¹ãã ãã©ã®ããã«æŽ»çšããŠããžã¿ã«çœ²åã®å®è£ ã匷åããæœåšçãªã©ã³ã¿ã€ã ãšã©ãŒã®å°é·åããã³ã³ãã€ã«æã®ã»ãã¥ãªãã£ä¿èšŒã®ç Šãžãšã³ãŒããå€é©ããããæ·±ãæãäžããŠãããŸããåºç€çãªæŠå¿µããå®è·µçã§çŸå®äžçã®ã³ãŒãäŸãžãšé²ã¿ãã°ããŒãã«ãªãªãŒãã£ãšã³ã¹ã®ããã«ãããå ç¢ã§ãä¿å®ããããããããŠå®èšŒå¯èœã§å®å šãªèªèšŒã·ã¹ãã ãæ§ç¯ããæ¹æ³ã瀺ããŸãã
åºç€ïŒããžã¿ã«çœ²åã®ç°¡åãªåŸ©ç¿
TypeScriptã®åœ¹å²ã«é£ã³èŸŒãåã«ãããžã¿ã«çœ²åãšã¯äœãããããŠãããã©ã®ããã«æ©èœããã®ãã«ã€ããŠãæç¢ºã§å ±éã®çè§£ã確ç«ããŸããããããã¯ææžã眲åã®ã¹ãã£ã³ç»å以äžã®ãã®ã§ããããã¯3ã€ã®äž»èŠãªæ±ã®äžã«æ§ç¯ããã匷åãªæå·ã¡ã«ããºã ã§ãã
第1ã®æ±ïŒããŒã¿å®å šæ§ã®ããã®ããã·ã¥å
ããªããææžãæã£ãŠãããšæ³åããŠãã ãããããªããç¥ããªããã¡ã«äžæåãããšã倿Žãããªãããã«ããããããã®ææžãããã·ã¥ã¢ã«ãŽãªãºã ïŒSHA-256ãªã©ïŒã«éããŸãããã®ã¢ã«ãŽãªãºã ã¯ãããã·ã¥ãŸãã¯ã¡ãã»ãŒãžãã€ãžã§ã¹ããšåŒã°ãããäžæã§åºå®ãµã€ãºã®æååãçæããŸããããã¯äžæ¹åã®ããã»ã¹ã§ããããã·ã¥ããå ã®ææžã埩å ããããšã¯ã§ããŸãããæãéèŠãªããšã¯ãå ã®ææžã®ãã£ã1ãããã§ã倿ŽããããšãçµæãšããŠåŸãããããã·ã¥ã¯å šãç°ãªããã®ã«ãªããšããããšã§ããããã«ããããŒã¿ã®å®å šæ§ãæäŸãããŸãã
第2ã®æ±ïŒçæ£æ§ãšåŠèªé²æ¢ã®ããã®é察称æå·
ããã§éæ³ãèµ·ãããŸããå ¬é鵿å·ãšããŠãç¥ãããé察称æå·ã¯ãåãŠãŒã¶ãŒã«å¯ŸããŠæ°åŠçã«é¢é£ä»ããããäžå¯Ÿã®éµãå«ã¿ãŸãïŒ
- ç§å¯éµïŒææè ã«ãã£ãŠçµ¶å¯Ÿã«ç§å¯ã«ä¿ãããŸããããã¯çœ²åã«äœ¿çšãããŸãã
- å ¬ééµïŒäžçäžã«èªç±ã«å ±æãããŸããããã¯æ€èšŒã«äœ¿çšãããŸãã
ç§å¯éµã§æå·åããããã®ã¯ã察å¿ããå ¬ééµã§ã®ã¿åŸ©å·ã§ããŸãããã®é¢ä¿ãä¿¡é Œã®åºç€ã§ãã
眲åãšæ€èšŒã®ããã»ã¹
ããããã¹ãŠãç°¡åãªã¯ãŒã¯ãããŒã§çµã³ã€ããŸãããïŒ
- 眲åïŒ
- ã¢ãªã¹ã¯çœ²åæžã¿ã®å¥çŽæžãããã«éããããšèããŠããŸãã
- 圌女ã¯ãŸããå¥çŽæžã®ããã·ã¥ãäœæããŸãã
- 次ã«ã圌女ã®ç§å¯éµã䜿ã£ãŠãã®ããã·ã¥ãæå·åããŸãããã®æå·åãããããã·ã¥ãããããžã¿ã«çœ²åã§ãã
- ã¢ãªã¹ã¯å ã®å¥çŽæžãšããžã¿ã«çœ²åãããã«éããŸãã
- æ€èšŒïŒ
- ããã¯å¥çŽæžãšçœ²åãåãåããŸãã
- 圌ã¯åãåã£ãå¥çŽæžãåããã¢ãªã¹ã䜿çšããã®ãšåãããã·ã¥ã¢ã«ãŽãªãºã ã䜿ã£ãŠãã®ããã·ã¥ãèšç®ããŸãã
- 次ã«ã圌ã¯ã¢ãªã¹ã®å ¬ééµïŒä¿¡é Œã§ããæ å ±æºããå ¥æå¯èœïŒã䜿ã£ãŠã圌女ãéã£ãŠãã眲åã埩å·ããŸããããã«ããã圌女ãèšç®ããå ã®ããã·ã¥ãæããã«ãªããŸãã
- ããã¯2ã€ã®ããã·ã¥ãæ¯èŒããŸãïŒåœŒèªèº«ãèšç®ãããã®ãšã眲åãã埩å·ãããã®ã§ãã
ããã·ã¥ãäžèŽããã°ãããã¯æ¬¡ã®3ã€ã®ããšã確信ã§ããŸãïŒ
- èªèšŒïŒç§å¯éµã®ææè ã§ããã¢ãªã¹ã ããã圌女ã®å ¬ééµã§åŸ©å·ã§ãã眲åãäœæã§ããã¯ãã§ãã
- å®å šæ§ïŒåœŒã®èšç®ããããã·ã¥ã眲åã®ãã®ãšäžèŽãããããææžã¯è»¢éäžã«å€æŽãããŠããŸããã
- åŠèªé²æ¢ïŒçœ²åãäœæããããã«å¿ èŠãªç§å¯éµã¯åœŒå¥³ããæã£ãŠããªããããã¢ãªã¹ã¯åŸã§ææžã«çœ²åããããšãåŠå®ã§ããŸããã
JavaScriptã®èª²é¡ïŒåé¢é£ã®è匱æ§ãæœãå Žæ
å®ç§ãªäžçã§ã¯ãäžèšã®ããã»ã¹ã¯æ¬ ç¹ããããŸããããããããœãããŠã§ã¢éçºã®çŸå®äžçãç¹ã«ãã¬ãŒã³ãªJavaScriptã§ã¯ãäºçްãªééãã倧ããªã»ãã¥ãªãã£ããŒã«ãçã¿åºãããšããããŸãã
Node.jsã«ãããå žåçãªæå·ã©ã€ãã©ãªé¢æ°ãèããŠã¿ãŸãããïŒ
// ä»®ã®ãã¬ãŒã³ãªJavaScript眲å颿°
function createSignature(data, privateKey, algorithm) {
const sign = crypto.createSign(algorithm);
sign.update(data);
sign.end();
const signature = sign.sign(privateKey, 'base64');
return signature;
}
ããã¯ååã«ã·ã³ãã«ã«èŠããŸãããäœãåé¡ã«ãªãããã§ããããïŒ
- `data`ã®äžæ£ãªããŒã¿åïŒ `sign.update()`ã¡ãœããã¯ãã°ãã°`string`ãŸãã¯`Buffer`ãæåŸ ããŸããéçºè ã誀ã£ãŠæ°å€ïŒ`12345`ïŒããªããžã§ã¯ãïŒ`{ id: 12345 }`ïŒãæž¡ããšãJavaScriptã¯ãããæé»çã«æååïŒ`"12345"`ãŸãã¯`"[object Object]"`ïŒã«å€æãããããããŸããã眲åã¯ãšã©ãŒãªãçæãããŸãããããã¯ééã£ãåºç€ããŒã¿ã«å¯Ÿãããã®ã«ãªããŸãããã®åŸã®æ€èšŒã¯å€±æããã€ã©ã€ã©ããããã蚺æãå°é£ãªãã°ã«ã€ãªãããŸãã
- äžé©åãªããŒåœ¢åŒã®æ±ãïŒ `sign.sign()`ã¡ãœããã¯`privateKey`ã®åœ¢åŒã«éåžžã«ããããã§ããPEM圢åŒã®æååã`KeyObject`ããŸãã¯`Buffer`ã§ããå¯èœæ§ããããŸããééã£ã圢åŒãéä¿¡ãããšãã©ã³ã¿ã€ã ã¯ã©ãã·ã¥ãåŒãèµ·ããããããã«æªãããšã«ãç¡å¹ãªçœ²åãçæããããšãããµã€ã¬ã³ããªå€±æã«ã€ãªããå¯èœæ§ããããŸãã
- `null`ãŸãã¯`undefined`ã®å€ïŒ ããŒã¿ããŒã¹ã®ã«ãã¯ã¢ããã«å€±æããŠ`privateKey`ã`undefined`ã«ãªã£ããã©ããªãã§ããããïŒã¢ããªã±ãŒã·ã§ã³ã¯å®è¡æã«ã¯ã©ãã·ã¥ããå éšã·ã¹ãã ã®ç¶æ³ãæŽé²ãããããµãŒãã¹æåŠïŒDoSïŒã®è匱æ§ãçã¿åºãããããå¯èœæ§ããããŸãã
- ã¢ã«ãŽãªãºã ã®äžäžèŽïŒ 眲å颿°ã`'sha256'`ã䜿çšããŠããã®ã«ãæ€èšŒåŽã`'sha512'`ã§çæããã眲åãæåŸ ããŠããå Žåãæ€èšŒã¯åžžã«å€±æããŸããåã·ã¹ãã ã«ãã匷å¶ããªããã°ãããã¯éçºè ã®èŠåŸãšããã¥ã¡ã³ããŒã·ã§ã³ã«ã®ã¿äŸåããããšã«ãªããŸãã
ãããã¯åãªãããã°ã©ãã³ã°ãšã©ãŒã§ã¯ãªããã»ãã¥ãªãã£äžã®æ¬ é¥ã§ããäžæ£ç¢ºã«çæããã眲åã¯ãæå¹ãªãã©ã³ã¶ã¯ã·ã§ã³ãæåŠãããããããè€éãªã·ããªãªã§ã¯ã眲åæäœã®ããã®æ»æãã¯ãã«ãéãããããå¯èœæ§ããããŸãã
TypeScriptã«ããææžïŒèªèšŒã®åå®å šæ§ã®å®è£
TypeScriptã¯ãã³ãŒããå®è¡ãããåã«ãããã®ã¯ã©ã¹ã®ãã°å šäœãæé€ããããŒã«ãæäŸããŸããããŒã¿æ§é ãšé¢æ°ã®ããã®åŒ·åãªå¥çŽãäœæããããšã«ããããšã©ãŒæ€åºãã©ã³ã¿ã€ã ããã³ã³ãã€ã«æã«ç§»è¡ããŸãã
ã¹ããã1ïŒäžæ žãšãªãæå·åã®å®çŸ©
æåã®ã¹ãããã¯ãæå·ããªããã£ããæç€ºçãªåã§ã¢ãã«åããããšã§ããæ±çšçãª`string`ã`any`ãæž¡ã代ããã«ãæ£ç¢ºãªã€ã³ã¿ãŒãã§ãŒã¹ãåãšã€ãªã¢ã¹ãå®çŸ©ããŸãã
ããã§ã®åŒ·åãªãã¯ããã¯ã¯ããã©ã³ãããã¿ã€ãïŒãŸãã¯ç§°ååïŒã䜿çšããããšã§ããããã«ãããæ§é çã«ã¯`string`ãšåäžã§ãããªããäºææ§ã®ãªããåºå¥ãããåãäœæã§ããŸããããã¯ããŒã眲åã«æé©ã§ãã
// types.ts
export type Brand
// ããŒãäžè¬çãªæååãšããŠæ±ãã¹ãã§ã¯ãªã
export type PrivateKey = Brand
export type PublicKey = Brand
// 眲åãç¹å®ã®çš®é¡ã®æååïŒäŸïŒbase64ïŒ
export type Signature = Brand
// ã¿ã€ããã¹ã誀çšãé²ãããã«èš±å¯ãããã¢ã«ãŽãªãºã ã®ã»ãããå®çŸ©
export enum SignatureAlgorithm {
RS256 = 'RSA-SHA256',
ES256 = 'ECDSA-SHA256',
// ããã«ä»ã®ãµããŒããããŠããã¢ã«ãŽãªãºã ã远å
}
// 眲åãããä»»æã®ããŒã¿ã®åºæ¬ã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©
export interface Signable {
// 眲åå¯èœãªãã€ããŒãã¯ã·ãªã¢ã©ã€ãºå¯èœã§ããããšã匷å¶ã§ãã
// ç°¡åã®ãããããã§ã¯ä»»æã®ãªããžã§ã¯ããèš±å¯ããããæ¬çªç°å¢ã§ã¯
// { [key: string]: string | number | boolean; } ã®ãããªæ§é ã匷å¶ãããããããªã
[key: string]: any;
}
ãããã®åã䜿çšãããšã`PrivateKey`ãæåŸ ãããå Žæã§`PublicKey`ã䜿çšããããšãããšãã³ã³ãã€ã©ããšã©ãŒãã¹ããŒããããã«ãªããŸããã©ããªã©ã³ãã ãªæååã§ãæž¡ããããã§ã¯ãªãããã©ã³ãããã¿ã€ãã«æç€ºçã«ãã£ã¹ãããå¿ èŠããããæç¢ºãªæå³ã瀺ãããšã«ãªããŸãã
ã¹ããã2ïŒåå®å šãªçœ²åããã³æ€èšŒé¢æ°ã®æ§ç¯
次ã«ããããã®åŒ·åãªåã䜿çšããŠé¢æ°ãæžãçŽããŸãããããã®äŸã§ã¯ãNode.jsã®çµã¿èŸŒã¿`crypto`ã¢ãžã¥ãŒã«ã䜿çšããŸãã
// crypto.service.ts
import * as crypto from 'crypto';
import { PrivateKey, PublicKey, Signature, SignatureAlgorithm, Signable } from './types';
export class DigitalSignatureService {
public sign
payload: T,
privateKey: PrivateKey,
algorithm: SignatureAlgorithm
): Signature {
// äžè²«æ§ãä¿ã€ãããåžžã«ãã€ããŒããæ±ºå®è«çãªæ¹æ³ã§æåååãã
// ããŒããœãŒãããããšã§ã{a:1, b:2} ãš {b:2, a:1} ãåãããã·ã¥ãçæããããšãä¿èšŒãã
const stringifiedPayload = JSON.stringify(payload, Object.keys(payload).sort());
const signer = crypto.createSign(algorithm);
signer.update(stringifiedPayload);
signer.end();
const signature = signer.sign(privateKey, 'base64');
return signature as Signature;
}
public verify
payload: T,
signature: Signature,
publicKey: PublicKey,
algorithm: SignatureAlgorithm
): boolean {
const stringifiedPayload = JSON.stringify(payload, Object.keys(payload).sort());
const verifier = crypto.createVerify(algorithm);
verifier.update(stringifiedPayload);
verifier.end();
return verifier.verify(publicKey, signature, 'base64');
}
}
颿°ã·ã°ããã£ã®éããèŠãŠãã ããïŒ
- `sign(payload: T, privateKey: PrivateKey, ...)`: `privateKey`ãšããŠèª€ã£ãŠå
¬ééµãäžè¬çãªæååãæž¡ãããšã¯ãã¯ãäžå¯èœã§ãããã€ããŒãã¯`Signable`ã€ã³ã¿ãŒãã§ãŒã¹ã«ãã£ãŠå¶çŽããããžã§ããªã¯ã¹ïŒ`
`ïŒã䜿çšããŠãã€ããŒãã®ç¹å®ã®åãä¿æããŸãã - `verify(..., signature: Signature, publicKey: PublicKey, ...)`: åŒæ°ãæç¢ºã«å®çŸ©ãããŠããŸãã眲åãšå ¬ééµãæ··åããããšã¯ã§ããŸããã
- `algorithm: SignatureAlgorithm`: enumã䜿çšããããšã§ãã¿ã€ããã¹ïŒ`'RSA-SHA256'` vs `'RSA-sha256'`ïŒãé²ããéçºè ãäºåã«æ¿èªãããå®å šãªã¢ã«ãŽãªãºã ã®ãªã¹ãã«å¶éããã³ã³ãã€ã«æã«æå·ã®ããŠã³ã°ã¬ãŒãæ»æãé²ããŸãã
ã¹ããã3ïŒJSON Web Tokens (JWT) ãçšããå®è·µçãªäŸ
ããžã¿ã«çœ²åã¯ãäžè¬çã«JSON Web Tokens (JWT) ã®äœæã«äœ¿çšãããJSON Web Signatures (JWS) ã®åºç€ã§ãããã®ãŠããã¿ã¹ãªèªèšŒã¡ã«ããºã ã«ãç§ãã¡ã®åå®å šãªãã¿ãŒã³ãé©çšããŠã¿ãŸãããã
ãŸããJWTãã€ããŒãã®å³å¯ãªåãå®çŸ©ããŸããæ±çšçãªãªããžã§ã¯ãã®ä»£ããã«ãæåŸ ããããã¹ãŠã®ã¯ã¬ãŒã ãšãã®åãæå®ããŸãã
// types.ts (æ¡åŒµ)
export interface UserTokenPayload extends Signable {
iss: string; // çºè¡è
(Issuer)
sub: string; // äž»äœ (Subject) (äŸïŒãŠãŒã¶ãŒID)
aud: string; // 察象è
(Audience)
exp: number; // æå¹æé (Unixã¿ã€ã ã¹ã¿ã³ã)
iat: number; // çºè¡æå» (Unixã¿ã€ã ã¹ã¿ã³ã)
jti: string; // JWT ID
roles: string[]; // ã«ã¹ã¿ã ã¯ã¬ãŒã
}
ããã§ãããŒã¯ã³çæããã³æ€èšŒãµãŒãã¹ã¯ããã®ç¹å®ã®ãã€ããŒãã«å¯ŸããŠå³å¯ã«åä»ãã§ããŸãã
// auth.service.ts
import { DigitalSignatureService } from './crypto.service';
import { PrivateKey, PublicKey, SignatureAlgorithm, UserTokenPayload } from './types';
class AuthService {
private signatureService = new DigitalSignatureService();
private privateKey: PrivateKey; // å®å
šã«èªã¿èŸŒãŸãã
private publicKey: PublicKey; // å
¬éãããŠãã
constructor(pk: PrivateKey, pub: PublicKey) {
this.privateKey = pk;
this.publicKey = pub;
}
// ãã®é¢æ°ã¯ãŠãŒã¶ãŒããŒã¯ã³ã®äœæã«ç¹åããŠãã
public generateUserToken(userId: string, roles: string[]): string {
const now = Math.floor(Date.now() / 1000);
const payload: UserTokenPayload = {
iss: 'https://api.my-global-app.com',
aud: 'my-global-app-clients',
sub: userId,
roles: roles,
iat: now,
exp: now + (60 * 15), // 15åéã®æå¹æé
jti: crypto.randomBytes(16).toString('hex'),
};
// JWSæšæºã¯base64ã§ã¯ãªãbase64urlãšã³ã³ãŒãã£ã³ã°ã䜿çšãã
const header = { alg: 'RS256', typ: 'JWT' }; // ã¢ã«ãŽãªãºã ã¯ããŒã®ã¿ã€ããšäžèŽããå¿
èŠããã
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
// æã
ã®åã·ã¹ãã ã¯JWSã®æ§é ãçè§£ããªãã®ã§ãæã
ãæ§ç¯ããå¿
èŠããã
// å®éã®ãããã¯ã·ã§ã³ã§ã¯ã©ã€ãã©ãªã䜿çšããããããã§ã¯åçã瀺ã
// 泚æïŒçœ²å㯠'encodedHeader.encodedPayload' æååã«å¯ŸããŠè¡ãããå¿
èŠããã
// ç°¡åã®ãããæã
ã®ãµãŒãã¹ã䜿ã£ãŠãã€ããŒããªããžã§ã¯ãã«çŽæ¥çœ²åãã
const signature = this.signatureService.sign(
payload,
this.privateKey,
SignatureAlgorithm.RS256
);
// é©åãªJWTã©ã€ãã©ãªãªã眲åã®base64url倿ãåŠçããŠãããã ãã
// ããã¯ãã€ããŒãã®åå®å
šæ§ã瀺ãããã®ç°¡ç¥åãããäŸã§ãã
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
public validateAndDecodeToken(token: string): UserTokenPayload | null {
// å®éã®ã¢ããªã§ã¯ 'jose' ã 'jsonwebtoken' ã®ãããªã©ã€ãã©ãªã䜿çšããã ãã
// ãããã¯è§£æãšæ€èšŒãåŠçããŠããã
const [header, payload, signature] = token.split('.');
if (!header || !payload || !signature) {
return null; // äžæ£ãªãã©ãŒããã
}
try {
const decodedPayload: unknown = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
// ããã§åã¬ãŒãã䜿ãããã³ãŒãããããªããžã§ã¯ããæ€èšŒãã
if (!this.isUserTokenPayload(decodedPayload)) {
console.error('ãã³ãŒãããããã€ããŒããæåŸ
ãããæ§é ãšäžèŽããŸããã');
return null;
}
// ããã§ decodedPayload ãå®å
šã« UserTokenPayload ãšããŠäœ¿çšã§ãã
const isValid = this.signatureService.verify(
decodedPayload,
signature as Signature, // ããã§ã¯stringãããã£ã¹ãããå¿
èŠããã
this.publicKey,
SignatureAlgorithm.RS256
);
if (!isValid) {
console.error('眲åã®æ€èšŒã«å€±æããŸããã');
return null;
}
if (decodedPayload.exp * 1000 < Date.now()) {
console.error('ããŒã¯ã³ã¯æå¹æéåãã§ãã');
return null;
}
return decodedPayload;
} catch (error) {
console.error('ããŒã¯ã³æ€èšŒäžã«ãšã©ãŒãçºçããŸãã:', error);
return null;
}
}
// ããã¯æ¥µããŠéèŠãªåã¬ãŒã颿°ã§ãã
private isUserTokenPayload(payload: unknown): payload is UserTokenPayload {
if (typeof payload !== 'object' || payload === null) return false;
const p = payload as { [key: string]: unknown };
return (
typeof p.iss === 'string' &&
typeof p.sub === 'string' &&
typeof p.aud === 'string' &&
typeof p.exp === 'number' &&
typeof p.iat === 'number' &&
typeof p.jti === 'string' &&
Array.isArray(p.roles) &&
p.roles.every(r => typeof r === 'string')
);
}
}
`isUserTokenPayload`åã¬ãŒãã¯ãåä»ããããŠããªãä¿¡é Œã§ããªãå€éšã®äžçïŒåä¿¡ããããŒã¯ã³æååïŒãšãå®å šã§åä»ããããå éšã·ã¹ãã ãšã®éã®æ©æž¡ããããŸãããã®é¢æ°ã`true`ãè¿ããåŸãTypeScriptã¯`decodedPayload`倿°ã`UserTokenPayload`ã€ã³ã¿ãŒãã§ãŒã¹ã«æºæ ããŠããããšãèªèãã`any`ãã£ã¹ãã`undefined`ãšã©ãŒã®æããªã`decodedPayload.sub`ã`decodedPayload.exp`ã®ãããªããããã£ãžã®å®å šãªã¢ã¯ã»ã¹ãå¯èœã«ããŸãã
ã¹ã±ãŒã©ãã«ãªåå®å šèªèšŒã®ããã®ã¢ãŒããã¯ãã£ãã¿ãŒã³
åå®å šæ§ãé©çšããããšã¯ãåã ã®é¢æ°ã ãã®è©±ã§ã¯ãããŸãããããã¯ãã»ãã¥ãªãã£å¥çŽãã³ã³ãã€ã©ã«ãã£ãŠåŒ·å¶ãããã·ã¹ãã å šäœãæ§ç¯ããããšã§ãã以äžã¯ããããã®å©ç¹ãæ¡åŒµããããã€ãã®ã¢ãŒããã¯ãã£ãã¿ãŒã³ã§ãã
åå®å šãªããŒãªããžããª
å€ãã®ã·ã¹ãã ã§ã¯ãæå·éµã¯ããŒç®¡çãµãŒãã¹ïŒKMSïŒã«ãã£ãŠç®¡çãããããå®å šãªä¿ç®¡åº«ã«ä¿åãããŸããéµãååŸãããšãã¯ããããæ£ããåã§è¿ãããããšãä¿èšŒãã¹ãã§ãã
`getKey(keyId: string): Promise
// key.repository.ts
import { PublicKey, PrivateKey } from './types';
interface KeyRepository {
getPublicKey(keyId: string): Promise
getPrivateKey(keyId: string): Promise
}
// å®è£
äŸïŒäŸïŒAWS KMSãAzure Key VaultããååŸïŒ
class KmsRepository implements KeyRepository {
public async getPublicKey(keyId: string): Promise
// ... KMSãåŒã³åºããŠå
¬é鵿ååãååŸããããžã㯠...
const keyFromKms: string | undefined = await someKmsSdk.getPublic(keyId);
if (!keyFromKms) return null;
return keyFromKms as PublicKey; // ãã©ã³ãããã¿ã€ãã«ãã£ã¹ã
}
public async getPrivateKey(keyId: string): Promise
// ... KMSãåŒã³åºããŠçœ²åã®ããã«ç§å¯éµã䜿çšããããžã㯠...
// å€ãã®KMSã·ã¹ãã ã§ã¯ãç§å¯éµèªäœã¯ååŸããã眲åããããŒã¿ãæž¡ã
// ãã®ãã¿ãŒã³ã¯è¿ããã眲åã«ãé©çšããã
return '... a securely retrieved key ...' as PrivateKey;
}
}
ãã®ã€ã³ã¿ãŒãã§ãŒã¹ã®èåŸã§éµã®ååŸãæœè±¡åããããšã«ãããã¢ããªã±ãŒã·ã§ã³ã®æ®ãã®éšåã¯KMS APIã®æååããŒã¹ã®æ§è³ªã«ã€ããŠå¿é ããå¿ èŠããªããªããŸãã`PublicKey`ãŸãã¯`PrivateKey`ãåãåãããšã«äŸåã§ããèªèšŒã¹ã¿ãã¯å šäœã§åå®å šæ§ã確ä¿ãããŸãã
å ¥åæ€èšŒã®ããã®ã¢ãµãŒã·ã§ã³é¢æ°
åã¬ãŒãã¯åªããŠããŸãããæ€èšŒã倱æããå Žåã«ããã«ãšã©ãŒãã¹ããŒãããå ŽåããããŸããTypeScriptã®`asserts`ããŒã¯ãŒãã¯ããã®ç®çã«æé©ã§ãã
// åã¬ãŒãã®ä¿®æ£ç
function assertIsUserTokenPayload(payload: unknown): asserts payload is UserTokenPayload {
if (!isUserTokenPayload(payload)) {
throw new Error('ç¡å¹ãªããŒã¯ã³ãã€ããŒãæ§é ã§ãã');
}
}
ããã§ãæ€èšŒããžãã¯ã§æ¬¡ã®ããã«ã§ããŸãïŒ
const decodedPayload: unknown = JSON.parse(...);
assertIsUserTokenPayload(decodedPayload);
// ãã®æç¹ãããTypeScriptã¯decodedPayloadãUserTokenPayloadåã§ããããšãç¥ã£ãŠãã
console.log(decodedPayload.sub); // ããã¯ä»ã100%åå®å
š
ãã®ãã¿ãŒã³ã¯ãæ€èšŒããžãã¯ãšãã®åŸã®ããžãã¹ããžãã¯ãåé¢ããããšã«ãããããã¯ãªãŒã³ã§èªã¿ãããæ€èšŒã³ãŒããäœæããŸãã
ã°ããŒãã«ãªåœ±é¿ãšäººçèŠå
å®å šãªã·ã¹ãã ãæ§ç¯ããããšã¯ãã³ãŒã以äžã®ãã®ãå«ãã°ããŒãã«ãªèª²é¡ã§ããããã«ã¯ãåœå¢ãã¿ã€ã ãŸãŒã³ãè¶ãã人ã ãããã»ã¹ããããŠååãé¢ãããŸããèªèšŒã®åå®å šæ§ã¯ããã®ã°ããŒãã«ãªæèã§å€§ããªå©ç¹ããããããŸãã
- çããããã¥ã¡ã³ããšããŠæ©èœããïŒåæ£ããããŒã ã«ãšã£ãŠãããåä»ããããã³ãŒãããŒã¹ã¯ãæ£ç¢ºã§ææ§ãã®ãªãããã¥ã¡ã³ãã®äžåœ¢æ ã§ããå¥ã®åœã®æ°ããéçºè ã¯ãåå®çŸ©ãèªãã ãã§èªèšŒã·ã¹ãã ã®ããŒã¿æ§é ãšå¥çŽãããã«çè§£ã§ããŸããããã«ãããèª€è§£ãæžãããªã³ããŒãã£ã³ã°ãéããªããŸãã
- ã»ãã¥ãªãã£ç£æ»ãç°¡çŽ åããïŒã»ãã¥ãªãã£ç£æ»äººãã³ãŒããã¬ãã¥ãŒããéãåå®å šãªå®è£ ã¯ã·ã¹ãã ã®æå³ãéåžžã«æç¢ºã«ããŸããæ£ããããŒãæ£ããæäœã«äœ¿çšãããŠããããšããããŒã¿æ§é ãäžè²«ããŠåŠçãããŠããããšãæ€èšŒããã®ã容æã«ãªããŸããããã¯ãSOC 2ãGDPRã®ãããªåœéæšæºãžã®æºæ ãéæããããã«éèŠã«ãªãããšããããŸãã
- çžäºéçšæ§ãåäžãããïŒTypeScriptã¯ã³ã³ãã€ã«æã®ä¿èšŒãæäŸããŸãããããŒã¿ã®éä¿¡ãã©ãŒãããã倿Žããããã§ã¯ãããŸãããåå®å šãªTypeScriptããã¯ãšã³ãã«ãã£ãŠçæãããJWTã¯ãäŸç¶ãšããŠæšæºçãªJWTã§ãããSwiftã§æžãããã¢ãã€ã«ã¯ã©ã€ã¢ã³ããGoã§æžãããããŒãããŒãµãŒãã¹ã«ãã£ãŠæ¶è²»ã§ããŸããåå®å šæ§ã¯ãã°ããŒãã«ã¹ã¿ã³ããŒããæ£ããå®è£ ããŠããããšãä¿èšŒããéçºæã®ã¬ãŒãã¬ãŒã«ã§ãã
- èªç¥è² è·ã軜æžããïŒæå·æè¡ã¯é£ãããã®ã§ããéçºè ã¯ãã·ã¹ãã å šäœã®ããŒã¿ãããŒãšåã®ã«ãŒã«ãé ã®äžã«ä¿æãç¶ããã¹ãã§ã¯ãããŸããããã®è²¬ä»»ãTypeScriptã³ã³ãã€ã©ã«å§è²ããããšã§ãéçºè ã¯`TypeError: cannot read property 'sign' of undefined`ã®ãããªããšãå¿é ããã®ã§ã¯ãªããæ£ããæå¹æéãã§ãã¯ãå ç¢ãªãšã©ãŒãã³ããªã³ã°ãšãã£ããããé«ã¬ãã«ã®ã»ãã¥ãªãã£ããžãã¯ã«éäžã§ããŸãã
çµè«ïŒåã§ä¿¡é Œãç¯ã
ããžã¿ã«çœ²åã¯çŸä»£ã®ããžã¿ã«ã»ãã¥ãªãã£ã®ç€ã§ãããJavaScriptã®ãããªåçåä»ãèšèªã§ã®å®è£ ã¯ãã»ãã®å°ããªãšã©ãŒãæ·±å»ãªçµæãæãå¯èœæ§ã®ããããªã±ãŒããªããã»ã¹ã§ããTypeScriptãæ¡çšããããšã§ãç§ãã¡ã¯åã«åã远å ããŠããã ãã§ãªããå®å šãªã³ãŒããæžãããã®ã¢ãããŒããæ ¹æ¬çã«å€ããŠããã®ã§ãã
æç€ºçãªåããã©ã³ãããããªããã£ããåã¬ãŒãããããŠææ ®æ·±ãã¢ãŒããã¯ãã£ãéããŠéæãããèªèšŒã®åå®å šæ§ã¯ã匷åãªã³ã³ãã€ã«æã®ã»ãŒããã£ããããæäŸããŸããããã«ãããäžè¬çãªè匱æ§ã«å¯ŸããŠããå ç¢ã§èæ§ãããã ãã§ãªããã°ããŒãã«ããŒã ã«ãšã£ãŠããçè§£ãããããä¿å®ãããããç£æ»ããããã·ã¹ãã ãæ§ç¯ããããšãå¯èœã«ãªããŸãã
çµå±ã®ãšãããå®å šãªã³ãŒããæžãããšã¯ãè€éãã管çããäžç¢ºå®æ§ãæå°éã«æããããšã§ããTypeScriptã¯ããŸãã«ãããå®çŸããããã®åŒ·åãªããŒã«ã»ãããæäŸããç§ãã¡ãçžäºæ¥ç¶ãããäžçãäŸåããããžã¿ã«ã®ä¿¡é ŒããäžåºŠã«äžã€ã®åå®å šãªé¢æ°ã以ãŠç¯ãäžããããšãå¯èœã«ããŠãããã®ã§ãã