Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AWS Cognito] Cognito에서 받은 Access Token을 람다에서 검증하기 #2

Open
sonypark opened this issue Oct 18, 2019 · 0 comments
Assignees
Labels
features Service features

Comments

@sonypark
Copy link
Owner

sonypark commented Oct 18, 2019

목표

클라이언트에서 받은 access token이 유효한지 검증한다.

프로세스

  1. 유저가 로그인을 하면 Cognito로부터 access token, id token, refresh token을 받는다.
  2. 유저는 로그인이 필요한 서비스(ex. 리뷰,평점 달기)를 사용시 access token을 백엔드 서버에 보낸다.
  3. access token은 request header에 담겨 온다.
  4. 백엔드 서버에서 access token을 받아 유효성 검사를 진행한다.
  5. 유효한 토큰이면 클라이언트에게 사용자 아이디와 검증 성공을 나타내는 표시를 전달하고, 유효하지 않은 토큰이면 에러 메시지를 보낸다.
import { promisify } from 'util';
import * as Axios from 'axios';
import * as jsonwebtoken from 'jsonwebtoken';
import * as jwkToPem from 'jwk-to-pem';

export interface ClaimVerifyRequest {
  readonly token?: string;
}

export interface ClaimVerifyResult {
  readonly userId: string;
  readonly clientId: string;
  readonly isValid: boolean;
  readonly error?: any;
}

interface TokenHeader {
  kid: string;
  alg: string;
}
interface PublicKey {
  alg: string;
  e: string;
  kid: string;
  kty: string;
  n: string;
  use: string;
}
interface PublicKeyMeta {
  instance: PublicKey;
  pem: string;
}

interface PublicKeys {
  keys: PublicKey[];
}

interface MapOfKidToPublicKey {
  [key: string]: PublicKeyMeta;
}

interface Claim {
  token_use: string;
  auth_time: number;
  iss: string;
  exp: number;
  username: string;
  client_id: string;
}

const cognitoRegion = process.env.COGNITO_REGION;
const cognitoPoolId = process.env.COGNITO_POOL_ID || '';
if (!cognitoPoolId) {
  throw new Error('env var required for cognito pool');
}

const cognitoIssuer = `https://cognito-idp.${cognitoRegion}.amazonaws.com/${cognitoPoolId}`;

let cacheKeys: MapOfKidToPublicKey | undefined;
const getPublicKeys = async (): Promise<MapOfKidToPublicKey> => {
  if (!cacheKeys) {
    const url = `${cognitoIssuer}/.well-known/jwks.json`;
    const publicKeys = await Axios.default.get<PublicKeys>(url);
    cacheKeys = publicKeys.data.keys.reduce(
      (agg, current: any) => {
        const pem = jwkToPem(current);
        agg[current.kid] = { instance: current, pem };
        return agg;
      },
      {} as MapOfKidToPublicKey
    );
    return cacheKeys;
  } else {
    return cacheKeys;
  }
};

const verifyPromised = promisify(jsonwebtoken.verify.bind(jsonwebtoken));

const handler = async (
  request: ClaimVerifyRequest
): Promise<ClaimVerifyResult> => {
  let result: ClaimVerifyResult;
  try {
    console.log(`User claim verfiy invoked for ${JSON.stringify(request)}`);
    console.log('TOKEN: ', request.token);
    const token = request.token;

    const tokenSections = (token || '').split('.');
    if (tokenSections.length < 2) {
      throw new Error('requested token is invalid');
    }
    const headerJSON = Buffer.from(tokenSections[0], 'base64').toString('utf8');
    const header = JSON.parse(headerJSON) as TokenHeader;
    const keys = await getPublicKeys();
    const key = keys[header.kid];
    if (key === undefined) {
      throw new Error('claim made for unknown kid');
    }
    const claim = (await verifyPromised(token, key.pem)) as Claim;
    const currentSeconds = Math.floor(new Date().valueOf() / 1000);
    if (currentSeconds > claim.exp || currentSeconds < claim.auth_time) {
      throw new Error('claim is expired or invalid');
    }
    if (claim.iss !== cognitoIssuer) {
      throw new Error('claim issuer is invalid');
    }
    if (claim.token_use !== 'access') {
      throw new Error('claim use is not access');
    }
    console.log(`claim confirmed for ${claim.username}`);
    result = {
      userId: claim.username,
      clientId: claim.client_id,
      isValid: true
    };
  } catch (error) {
    console.log(error);
    result = { userId: '', clientId: '', error, isValid: false };
  }
  return result;
};

module.exports = handler;

참고 링크

@sonypark sonypark added the features Service features label Oct 18, 2019
@sonypark sonypark self-assigned this Oct 18, 2019
@sonypark sonypark changed the title [Cognito] Cognito에서 받은 Access Token을 람다에서 검증하기 [AWS Cognito] Cognito에서 받은 Access Token을 람다에서 검증하기 Oct 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
features Service features
Projects
None yet
Development

No branches or pull requests

1 participant