์„œ๋ช…๋œ ํ—ค๋”๋กœ ์•ฑ ๋ณด์•ˆ ๊ฐ•ํ™”

์ด ํŽ˜์ด์ง€์—์„œ๋Š” ์„œ๋ช…๋œ IAP ํ—ค๋”๋กœ ์•ฑ์„ ๋ณดํ˜ธํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. IAP(Identity-Aware Proxy)๊ฐ€ ๊ตฌ์„ฑ๋˜๋ฉด JWT(JSON ์›น ํ† ํฐ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ์— ๋Œ€ํ•œ ์š”์ฒญ์ด ์Šน์ธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ๋‹ค์Œ ์ข…๋ฅ˜์˜ ์œ„ํ—˜์œผ๋กœ๋ถ€ํ„ฐ ์•ฑ์„ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค.

  • ์‹ค์ˆ˜๋กœ ์‚ฌ์šฉ ์ค‘์ง€๋œ IAP
  • ์ž˜๋ชป ๊ตฌ์„ฑ๋œ ๋ฐฉํ™”๋ฒฝ
  • ํ”„๋กœ์ ํŠธ ๋‚ด๋ถ€์—์„œ์˜ ์•ก์„ธ์Šค

์•ฑ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ณดํ˜ธํ•˜๋ ค๋ฉด ๋ชจ๋“  ์•ฑ ์œ ํ˜•์— ์„œ๋ช…๋œ ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋˜๋Š” App Engine ํ‘œ์ค€ ํ™˜๊ฒฝ ์•ฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ Users API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Compute Engine ๋ฐ GKE ์ƒํƒœ ์ ๊ฒ€์€ JWT ํ—ค๋”๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉฐ IAP๋Š” ์ƒํƒœ ์ ๊ฒ€์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ์ ๊ฒ€์ด ์•ก์„ธ์Šค ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ์šฐ, Google Cloud ์ฝ˜์†”์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  JWT ํ—ค๋” ๊ฒ€์ฆ์—์„œ ์ƒํƒœ ์ ๊ฒ€ ๊ฒฝ๋กœ๋ฅผ ํ—ˆ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ƒํƒœ ์ ๊ฒ€ ์˜ˆ์™ธ ๋งŒ๋“ค๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์‹œ์ž‘ํ•˜๊ธฐ ์ „์—

์„œ๋ช…๋œ ํ—ค๋”๋กœ ์•ฑ์„ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

IAP ํ—ค๋”๋กœ ์•ฑ ๋ณดํ˜ธ

IAP JWT๋กœ ์•ฑ์„ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” JWT์˜ ํ—ค๋”, ํŽ˜์ด๋กœ๋“œ, ์„œ๋ช…์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. JWT๋Š” HTTP ์š”์ฒญ ํ—ค๋” x-goog-iap-jwt-assertion์— ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ IAP๋ฅผ ์šฐํšŒํ•  ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž๋Š” IAP ๋น„์„œ๋ช… ID ํ—ค๋”์ธ x-goog-authenticated-user-{email,id}๋ฅผ ์œ„์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. IAP JWT๋Š” ๋ณด๋‹ค ์•ˆ์ „ํ•œ ๋Œ€์•ˆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ช…๋œ ํ—ค๋”๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด IAP๋ฅผ ์šฐํšŒํ•  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๋ณด์กฐ ๋ณด์•ˆ ์ˆ˜๋‹จ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. IAP๊ฐ€ ์‚ฌ์šฉ ์„ค์ •๋˜๋ฉด ์š”์ฒญ์ด IAP ์ œ๊ณต ์ธํ”„๋ผ๋ฅผ ํ†ต๊ณผํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ๊ณตํ•œ x-goog-* ํ—ค๋”๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

JWT ํ—ค๋” ํ™•์ธ

JWT ํ—ค๋”๊ฐ€ ๋‹ค์Œ ์ œ์•ฝ์กฐ๊ฑด์„ ๋”ฐ๋ฅด๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

JWT ํ—ค๋” ํด๋ ˆ์ž„
alg ์•Œ๊ณ ๋ฆฌ์ฆ˜ ES256
kid ํ‚ค ID https://www.gstatic.com/iap/verify/public_key ๋ฐ https://www.gstatic.com/iap/verify/public_key-jwk์˜ ๋‘ ๊ฐ€์ง€ ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ IAP ํ‚ค ํŒŒ์ผ์— ๋‚˜์—ด๋œ ๊ณต๊ฐœ ํ‚ค ์ค‘ ํ•˜๋‚˜์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ† ํฐ์˜ kid ํด๋ ˆ์ž„์— ํ•ด๋‹นํ•˜๋Š” ๋น„๊ณต๊ฐœ ํ‚ค๋กœ JWT์— ์„œ๋ช…ํ–ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € ๋‹ค์Œ ๋‘ ์œ„์น˜ ์ค‘ ํ•˜๋‚˜์—์„œ ๊ณต๊ฐœ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

  • https://www.gstatic.com/iap/verify/public_key. ์ด URL์—๋Š” kid ํด๋ ˆ์ž„์„ ๊ณต๊ฐœ ํ‚ค ๊ฐ’์— ๋งคํ•‘ํ•˜๋Š” JSON ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • https://www.gstatic.com/iap/verify/public_key-jwk. ์ด URL์—๋Š” JWK ํ˜•์‹์˜ IAP ๊ณต๊ฐœ ํ‚ค๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๊ณต๊ฐœ ํ‚ค๊ฐ€ ์ค€๋น„๋œ ๋‹ค์Œ์—๋Š” JWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ช…์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

JWT ํŽ˜์ด๋กœ๋“œ ํ™•์ธ

JWT์˜ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ๋‹ค์Œ ์ œ์•ฝ์กฐ๊ฑด์„ ๋”ฐ๋ฅด๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

JWT ํŽ˜์ด๋กœ๋“œ ํด๋ ˆ์ž„
exp ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋ฏธ๋ž˜ ์‹œ๊ฐ„์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. UNIX ๊ธฐ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์ธก์ •ํ•œ ์‹œ๊ฐ„(์ดˆ)์ž…๋‹ˆ๋‹ค. ๋ณด์ •๊ฐ’์€ 30์ดˆ๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ† ํฐ์˜ ์ตœ๋Œ€ ์ˆ˜๋ช…์€ 10๋ถ„ + 2 * ๋ณด์ •๊ฐ’์ž…๋‹ˆ๋‹ค.
iat ๋ฐœ๊ธ‰ ์‹œ๊ฐ„ ๊ณผ๊ฑฐ ์‹œ๊ฐ„์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. UNIX ๊ธฐ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์ธก์ •ํ•œ ์‹œ๊ฐ„(์ดˆ)์ž…๋‹ˆ๋‹ค. ๋ณด์ •๊ฐ’์€ 30์ดˆ๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.
aud ๋Œ€์ƒ ๋‹ค์Œ ๊ฐ’์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • App Engine: /projects/PROJECT_NUMBER/apps/PROJECT_ID
  • Compute Engine ๋ฐ GKE: /projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID
iss ๋ฐœ๊ธ‰์ž https://cloud.google.com/iap์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
hd ๊ณ„์ • ๋„๋ฉ”์ธ ๊ณ„์ •์ด ํ˜ธ์ŠคํŒ…๋œ ๋„๋ฉ”์ธ์— ์†ํ•œ ๊ฒฝ์šฐ, ๊ณ„์ •์ด ์—ฐ๊ฒฐ๋œ ๋„๋ฉ”์ธ์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด hd ํด๋ ˆ์ž„์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
google Google ํด๋ ˆ์ž„ ํ•˜๋‚˜ ์ด์ƒ์˜ ์•ก์„ธ์Šค ์ˆ˜์ค€์ด ์š”์ฒญ์— ์ ์šฉ๋  ๊ฒฝ์šฐ ํ•ด๋‹น ์ด๋ฆ„์ด google ํด๋ ˆ์ž„์˜ JSON ๊ฐ์ฒด ๋‚ด๋ถ€์˜ access_levels ํ‚ค ์•„๋ž˜์— ๋ฌธ์ž์—ด ๋ฐฐ์—ด๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๊ธฐ ์ •์ฑ…์„ ์ง€์ •ํ•˜๊ณ  ์กฐ์ง์— ๊ธฐ๊ธฐ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ๊ถŒํ•œ์ด ์žˆ์œผ๋ฉด DeviceId๋„ JSON ๊ฐ์ฒด์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์กฐ์ง์œผ๋กœ ์ด๋™ํ•˜๋Š” ์š”์ฒญ์—๋Š” ๊ธฐ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Google Cloud ์ฝ˜์†”์— ์•ก์„ธ์Šคํ•˜์—ฌ ์œ„์— ์„ค๋ช…๋œ aud ๋ฌธ์ž์—ด์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ gcloud ๋ช…๋ น์ค„ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Google Cloud ์ฝ˜์†”์—์„œ aud ๋ฌธ์ž์—ด ๊ฐ’์„ ์–ป์œผ๋ ค๋ฉด ํ”„๋กœ์ ํŠธ์˜ Identity-Aware Proxy ์„ค์ •์œผ๋กœ ์ด๋™ํ•˜์—ฌ ๋ถ€ํ•˜ ๋ถ„์‚ฐ๊ธฐ ๋ฆฌ์†Œ์Šค ์˜†์— ์žˆ๋Š” ๋”๋ณด๊ธฐ๋ฅผ ํด๋ฆญํ•œ ํ›„ ์„œ๋ช…๋œ ํ—ค๋” JWT ๋Œ€์ƒ์„ ์„ ํƒํ•˜์„ธ์š”. ํ‘œ์‹œ๋˜๋Š” ์„œ๋ช…๋œ ํ—ค๋” JWT ๋Œ€ํ™”์ƒ์ž์— ์„ ํƒ๋œ ๋ฆฌ์†Œ์Šค์˜ aud ํด๋ ˆ์ž„์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์„œ๋ช…๋œ ํ—ค๋” JWT ๋Œ€์ƒ ์˜ต์…˜์ด ํฌํ•จ๋œ ๋”๋ณด๊ธฐ ๋ฉ”๋‰ด

gcloud CLI gcloud ๋ช…๋ น์ค„ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ aud ๋ฌธ์ž์—ด ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ํ”„๋กœ์ ํŠธ ID๋ฅผ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. Google Cloud ์ฝ˜์†” ํ”„๋กœ์ ํŠธ ์ •๋ณด ์นด๋“œ์—์„œ ํ”„๋กœ์ ํŠธ ID๋ฅผ ์ฐพ์€ ๋‹ค์Œ ๊ฐ ๊ฐ’์— ๋Œ€ํ•ด ์•„๋ž˜ ์ง€์ •๋œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋ฒˆํ˜ธ

gcloud ๋ช…๋ น์ค„ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”.

gcloud projects describe PROJECT_ID

์ด ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ ์ถœ๋ ฅ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

createTime: '2016-10-13T16:44:28.170Z'
lifecycleState: ACTIVE
name: project_name
parent:
  id: '433637338589'
  type: organization
projectId: PROJECT_ID
projectNumber: 'PROJECT_NUMBER'

์„œ๋น„์Šค ID

gcloud ๋ช…๋ น์ค„ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋น„์Šค ID๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”.

gcloud compute backend-services describe SERVICE_NAME --project=PROJECT_ID --global

์ด ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ ์ถœ๋ ฅ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

affinityCookieTtlSec: 0
backends:
- balancingMode: UTILIZATION
  capacityScaler: 1.0
  group: https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/instanceGroups/my-group
connectionDraining:
  drainingTimeoutSec: 0
creationTimestamp: '2017-04-03T14:01:35.687-07:00'
description: ''
enableCDN: false
fingerprint: zaOnO4k56Cw=
healthChecks:
- https://www.googleapis.com/compute/v1/projects/project_name/global/httpsHealthChecks/my-hc
id: 'SERVICE_ID'
kind: compute#backendService
loadBalancingScheme: EXTERNAL
name: my-service
port: 8443
portName: https
protocol: HTTPS
selfLink: https://www.googleapis.com/compute/v1/projects/project_name/global/backendServices/my-service
sessionAffinity: NONE
timeoutSec: 3610

์‚ฌ์šฉ์ž ID ๊ฒ€์ƒ‰

์œ„์˜ ๋ชจ๋“  ํ™•์ธ์ด ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ID๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ID ํ† ํฐ์˜ ํŽ˜์ด๋กœ๋“œ์—๋Š” ๋‹ค์Œ ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

ID ํ† ํฐ ํŽ˜์ด๋กœ๋“œ ์‚ฌ์šฉ์ž ID
sub ์ œ๋ชฉ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ณ ์œ ํ•˜๊ณ  ์•ˆ์ •์ ์ธ ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค. x-goog-authenticated-user-id ํ—ค๋” ๋Œ€์‹  ์ด ๊ฐ’์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
email ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค.
  • x-goog-authenticated-user-email ํ—ค๋” ๋Œ€์‹  ์ด ๊ฐ’์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ•ด๋‹น ํ—ค๋” ๋ฐ sub ํด๋ ˆ์ž„๊ณผ ๋‹ฌ๋ฆฌ ์ด ๊ฐ’์—๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค ํ”„๋ฆฌํ”ฝ์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์„œ๋ช…๋œ IAP ํ—ค๋”๋กœ ์•ฑ์„ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ์ƒ˜ํ”Œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

C#


using Google.Apis.Auth;
using Google.Apis.Auth.OAuth2;
using System;
using System.Threading;
using System.Threading.Tasks;

public class IAPTokenVerification
{
    /// <summary>
    /// Verifies a signed jwt token and returns its payload.
    /// </summary>
    /// <param name="signedJwt">The token to verify.</param>
    /// <param name="expectedAudience">The audience that the token should be meant for.
    /// Validation will fail if that's not the case.</param>
    /// <param name="cancellationToken">The cancellation token to propagate cancellation requests.</param>
    /// <returns>A task that when completed will have as its result the payload of the verified token.</returns>
    /// <exception cref="InvalidJwtException">If verification failed. The message of the exception will contain
    /// information as to why the token failed.</exception>
    public async Task<JsonWebSignature.Payload> VerifyTokenAsync(
        string signedJwt, string expectedAudience, CancellationToken cancellationToken = default)
    {
        SignedTokenVerificationOptions options = new SignedTokenVerificationOptions
        {
            // Use clock tolerance to account for possible clock differences
            // between the issuer and the verifier.
            IssuedAtClockTolerance = TimeSpan.FromMinutes(1),
            ExpiryClockTolerance = TimeSpan.FromMinutes(1),
            TrustedAudiences = { expectedAudience },
            TrustedIssuers = { "https://cloud.google.com/iap" },
            CertificatesUrl = GoogleAuthConsts.IapKeySetUrl,
        };

        return await JsonWebSignature.VerifySignedTokenAsync(signedJwt, options, cancellationToken: cancellationToken);
    }
}

Go

import (
	"context"
	"fmt"
	"io"

	"google.golang.org/api/idtoken"
)

// validateJWTFromAppEngine validates a JWT found in the
// "x-goog-iap-jwt-assertion" header.
func validateJWTFromAppEngine(w io.Writer, iapJWT, projectNumber, projectID string) error {
	// iapJWT := "YmFzZQ==.ZW5jb2RlZA==.and0" // req.Header.Get("X-Goog-IAP-JWT-Assertion")
	// projectNumber := "123456789"
	// projectID := "your-project-id"
	ctx := context.Background()
	aud := fmt.Sprintf("/projects/%s/apps/%s", projectNumber, projectID)

	payload, err := idtoken.Validate(ctx, iapJWT, aud)
	if err != nil {
		return fmt.Errorf("idtoken.Validate: %w", err)
	}

	// payload contains the JWT claims for further inspection or validation
	fmt.Fprintf(w, "payload: %v", payload)

	return nil
}

// validateJWTFromComputeEngine validates a JWT found in the
// "x-goog-iap-jwt-assertion" header.
func validateJWTFromComputeEngine(w io.Writer, iapJWT, projectNumber, backendServiceID string) error {
	// iapJWT := "YmFzZQ==.ZW5jb2RlZA==.and0" // req.Header.Get("X-Goog-IAP-JWT-Assertion")
	// projectNumber := "123456789"
	// backendServiceID := "backend-service-id"
	ctx := context.Background()
	aud := fmt.Sprintf("/projects/%s/global/backendServices/%s", projectNumber, backendServiceID)

	payload, err := idtoken.Validate(ctx, iapJWT, aud)
	if err != nil {
		return fmt.Errorf("idtoken.Validate: %w", err)
	}

	// payload contains the JWT claims for further inspection or validation
	fmt.Fprintf(w, "payload: %v", payload)

	return nil
}

Java


import com.google.api.client.http.HttpRequest;
import com.google.api.client.json.webtoken.JsonWebToken;
import com.google.auth.oauth2.TokenVerifier;

/** Verify IAP authorization JWT token in incoming request. */
public class VerifyIapRequestHeader {

  private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";

  // Verify jwt tokens addressed to IAP protected resources on App Engine.
  // The project *number* for your Google Cloud project via 'gcloud projects describe $PROJECT_ID'
  // The project *number* can also be retrieved from the Project Info card in Cloud Console.
  // projectId is The project *ID* for your Google Cloud Project.
  boolean verifyJwtForAppEngine(HttpRequest request, long projectNumber, String projectId)
      throws Exception {
    // Check for iap jwt header in incoming request
    String jwt = request.getHeaders().getFirstHeaderStringValue("x-goog-iap-jwt-assertion");
    if (jwt == null) {
      return false;
    }
    return verifyJwt(
        jwt,
        String.format("/projects/%s/apps/%s", Long.toUnsignedString(projectNumber), projectId));
  }

  boolean verifyJwtForComputeEngine(HttpRequest request, long projectNumber, long backendServiceId)
      throws Exception {
    // Check for iap jwt header in incoming request
    String jwtToken = request.getHeaders().getFirstHeaderStringValue("x-goog-iap-jwt-assertion");
    if (jwtToken == null) {
      return false;
    }
    return verifyJwt(
        jwtToken,
        String.format(
            "/projects/%s/global/backendServices/%s",
            Long.toUnsignedString(projectNumber), Long.toUnsignedString(backendServiceId)));
  }

  private boolean verifyJwt(String jwtToken, String expectedAudience) {
    TokenVerifier tokenVerifier =
        TokenVerifier.newBuilder().setAudience(expectedAudience).setIssuer(IAP_ISSUER_URL).build();
    try {
      JsonWebToken jsonWebToken = tokenVerifier.verify(jwtToken);

      // Verify that the token contain subject and email claims
      JsonWebToken.Payload payload = jsonWebToken.getPayload();
      return payload.getSubject() != null && payload.get("email") != null;
    } catch (TokenVerifier.VerificationException e) {
      System.out.println(e.getMessage());
      return false;
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const iapJwt = 'SOME_ID_TOKEN'; // JWT from the "x-goog-iap-jwt-assertion" header

let expectedAudience = null;
if (projectNumber && projectId) {
  // Expected Audience for App Engine.
  expectedAudience = `/projects/${projectNumber}/apps/${projectId}`;
} else if (projectNumber && backendServiceId) {
  // Expected Audience for Compute Engine
  expectedAudience = `/projects/${projectNumber}/global/backendServices/${backendServiceId}`;
}

const oAuth2Client = new OAuth2Client();

async function verify() {
  // Verify the id_token, and access the claims.
  const response = await oAuth2Client.getIapPublicKeys();
  const ticket = await oAuth2Client.verifySignedJwtWithCertsAsync(
    iapJwt,
    response.pubkeys,
    expectedAudience,
    ['https://cloud.google.com/iap'],
  );
  // Print out the info contained in the IAP ID token
  console.log(ticket);
}

verify().catch(console.error);

PHP

namespace Google\Cloud\Samples\Iap;

# Imports Google auth libraries for IAP validation
use Google\Auth\AccessToken;

/**
 * Validate a JWT passed to your App Engine app by Identity-Aware Proxy.
 *
 * @param string $iapJwt The contents of the X-Goog-IAP-JWT-Assertion header.
 * @param string $cloudProjectNumber The project *number* for your Google
 *     Cloud project. This is returned by 'gcloud projects describe $PROJECT_ID',
 *     or in the Project Info card in Cloud Console.
 * @param string $cloudProjectId Your Google Cloud Project ID.
 */
function validate_jwt_from_app_engine(
    string $iapJwt,
    string $cloudProjectNumber,
    string $cloudProjectId
): void {
    $expectedAudience = sprintf(
        '/projects/%s/apps/%s',
        $cloudProjectNumber,
        $cloudProjectId
    );
    validate_jwt($iapJwt, $expectedAudience);
}

/**
 * Validate a JWT passed to your Compute / Container Engine app by Identity-Aware Proxy.
 *
 * @param string $iapJwt The contents of the X-Goog-IAP-JWT-Assertion header.
 * @param string $cloudProjectNumber The project *number* for your Google
 *     Cloud project. This is returned by 'gcloud projects describe $PROJECT_ID',
 *     or in the Project Info card in Cloud Console.
 * @param string $backendServiceId The ID of the backend service used to access the
 *     application. See https://cloud.google.com/iap/docs/signed-headers-howto
 *     for details on how to get this value.
 */
function validate_jwt_from_compute_engine(
    string $iapJwt,
    string $cloudProjectNumber,
    string $backendServiceId
): void {
    $expectedAudience = sprintf(
        '/projects/%s/global/backendServices/%s',
        $cloudProjectNumber,
        $backendServiceId
    );
    validate_jwt($iapJwt, $expectedAudience);
}

/**
 * Validate a JWT passed to your app by Identity-Aware Proxy.
 *
 * @param string $iapJwt The contents of the X-Goog-IAP-JWT-Assertion header.
 * @param string $expectedAudience The expected audience of the JWT with the following formats:
 *     App Engine:     /projects/{PROJECT_NUMBER}/apps/{PROJECT_ID}
 *     Compute Engine: /projects/{PROJECT_NUMBER}/global/backendServices/{BACKEND_SERVICE_ID}
 */
function validate_jwt(string $iapJwt, string $expectedAudience): void
{
    // Validate the signature using the IAP cert URL.
    $token = new AccessToken();
    $jwt = $token->verify($iapJwt, [
        'certsLocation' => AccessToken::IAP_CERT_URL
    ]);

    if (!$jwt) {
        print('Failed to validate JWT: Invalid JWT');
        return;
    }

    // Validate token by checking issuer and audience fields.
    assert($jwt['iss'] == 'https://cloud.google.com/iap');
    assert($jwt['aud'] == $expectedAudience);

    print('Printing user identity information from ID token payload:');
    printf('sub: %s', $jwt['sub']);
    printf('email: %s', $jwt['email']);
}

Python

from google.auth.transport import requests
from google.oauth2 import id_token


def validate_iap_jwt(iap_jwt, expected_audience):
    """Validate an IAP JWT.

    Args:
      iap_jwt: The contents of the X-Goog-IAP-JWT-Assertion header.
      expected_audience: The Signed Header JWT audience. See
          https://cloud.google.com/iap/docs/signed-headers-howto
          for details on how to get this value.

    Returns:
      (user_id, user_email, error_str).
    """

    try:
        decoded_jwt = id_token.verify_token(
            iap_jwt,
            requests.Request(),
            audience=expected_audience,
            certs_url="https://www.gstatic.com/iap/verify/public_key",
        )
        return (decoded_jwt["sub"], decoded_jwt["email"], "")
    except Exception as e:
        return (None, None, f"**ERROR: JWT validation error {e}**")

Ruby

# iap_jwt = "The contents of the X-Goog-Iap-Jwt-Assertion header"
# project_number = "The project *number* for your Google Cloud project"
# project_id = "Your Google Cloud project ID"
# backend_service_id = "Your Compute Engine backend service ID"
require "googleauth"

audience = nil
if project_number && project_id
  # Expected audience for App Engine
  audience = "/projects/#{project_number}/apps/#{project_id}"
elsif project_number && backend_service_id
  # Expected audience for Compute Engine
  audience = "/projects/#{project_number}/global/backendServices/#{backend_service_id}"
end

# The client ID as the target audience for IAP
payload = Google::Auth::IDTokens.verify_iap iap_jwt, aud: audience

puts payload

if audience.nil?
  puts "Audience not verified! Supply a project_number and project_id to verify"
end

๊ฒ€์ฆ ์ฝ”๋“œ ํ…Œ์ŠคํŠธ

secure_token_test ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ์œผ๋กœ ์ด๋™ํ•˜๋ฉด IAP์— ์ž˜๋ชป๋œ JWT๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JWT ๊ฒ€์ฆ ๋กœ์ง์ด ๋‹ค์–‘ํ•œ ์‹คํŒจ ์‚ฌ๋ก€๋ฅผ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ž˜๋ชป๋œ JWT๋ฅผ ๋ฐ›์„ ๋•Œ์˜ ์•ฑ ๋™์ž‘์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ƒํƒœ ์ ๊ฒ€ ์˜ˆ์™ธ ๋งŒ๋“ค๊ธฐ

์•ž์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ Compute Engine ๋ฐ GKE ์ƒํƒœ ์ ๊ฒ€์€ JWT ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ IAP๋Š” ์ƒํƒœ ์ ๊ฒ€์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ์ ๊ฒ€์„ ๊ตฌ์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๊ณ , ์•ฑ์ด ์ƒํƒœ ์ ๊ฒ€์„ ํ—ˆ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ƒํƒœ ์ ๊ฒ€ ๊ตฌ์„ฑ

์ƒํƒœ ์ ๊ฒ€์— ๋Œ€ํ•ด ์•„์ง ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ,Google Cloud ์ฝ˜์†”์„ ์‚ฌ์šฉํ•ด์„œ ์ƒํƒœ ์ ๊ฒ€์— ๋Œ€ํ•ด ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ๋กœ๋Š” ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค์— ์˜ํ•ด ๊ณต์œ ๋˜์ง€ ์•Š๋„๋ก ํ•˜์„ธ์š”.

  1. Google Cloud ์ฝ˜์†”์˜ ์ƒํƒœ ์ ๊ฒ€ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
    ์ƒํƒœ ์ ๊ฒ€ ํŽ˜์ด์ง€๋กœ ์ด๋™
  2. ์•ฑ์— ์‚ฌ์šฉ ์ค‘์ธ ์ƒํƒœ ์ ๊ฒ€์„ ํด๋ฆญํ•œ ํ›„ ์ˆ˜์ •์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  3. ์š”์ฒญ ๊ฒฝ๋กœ ์•„๋ž˜์—์„œ ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ๋กœ ์ด๋ฆ„์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Google Cloud ๊ฐ€ ์ƒํƒœ ์ ๊ฒ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” URL ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋žตํ•˜๋ฉด ์ƒํƒœ ์ ๊ฒ€ ์š”์ฒญ์ด /๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.
  4. ์ €์žฅ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

JWT ๊ฒ€์ฆ ๊ตฌ์„ฑ

JWT ๊ฒ€์ฆ ๋ฃจํ‹ด์„ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ์—์„œ ์ƒํƒœ ์ ๊ฒ€ ์š”์ฒญ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด 200 HTTP ์ƒํƒœ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

if HttpRequest.path_info = '/HEALTH_CHECK_REQUEST_PATH'
  return HttpResponse(status=200)
else
  VALIDATION_FUNCTION

์™ธ๋ถ€ ID๋ฅผ ์œ„ํ•œ JWT

์™ธ๋ถ€ ID์™€ ํ•จ๊ป˜ IAP๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ IAP๋Š” Google ID์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ธ์ฆ๋œ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์„œ๋ช…๋œ JWT๋ฅผ ๊ณ„์† ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ์ฐจ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ๊ณต์—…์ฒด ์ •๋ณด

์™ธ๋ถ€ ID๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ JWT ํŽ˜์ด๋กœ๋“œ์—๋Š” gcip๋ผ๋Š” ํด๋ ˆ์ž„์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด ํด๋ ˆ์ž„์—๋Š” ์ด๋ฉ”์ผ ๋ฐ ์‚ฌ์ง„ URL๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด์™€ ์ถ”๊ฐ€ ์ œ๊ณต์—…์ฒด๋ณ„ ์†์„ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ Facebook์œผ๋กœ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ JWT ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

"gcip": '{
  "auth_time": 1553219869,
  "email": "facebook_user@gmail.com",
  "email_verified": false,
  "firebase": {
    "identities": {
      "email": [
        "facebook_user@gmail.com"
      ],
      "facebook.com": [
        "1234567890"
      ]
    },
    "sign_in_provider": "facebook.com",
  },
  "name": "Facebook User",
  "picture: "https://graph.facebook.com/1234567890/picture",
  "sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',

email ๋ฐ sub ํ•„๋“œ

Identity Platform์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•œ ๊ฒฝ์šฐ JWT์˜ email ๋ฐ sub ํ•„๋“œ ์•ž์— Identity Platform ํ† ํฐ ๋ฐœ๊ธ‰๊ธฐ๊ด€๊ณผ ์‚ฌ์šฉ๋œ ํ…Œ๋„ŒํŠธ ID(์žˆ๋Š” ๊ฒฝ์šฐ)๊ฐ€ ํ”„๋ฆฌํ”ฝ์Šค๋กœ ๋ถ™์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

"email": "securetoken.google.com/PROJECT-ID/TENANT-ID:demo_user@gmail.com",
"sub": "securetoken.google.com/PROJECT-ID/TENANT-ID:gZG0yELPypZElTmAT9I55prjHg63"

sign_in_attributes๋กœ ์•ก์„ธ์Šค ์ œ์–ด

IAM์€ ์™ธ๋ถ€ ID์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์ง€๋งŒ ๋Œ€์‹  sign_in_attributes ํ•„๋“œ์— ํฌํ•จ๋œ ํด๋ ˆ์ž„์„ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์„ธ์Šค๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, SAML ์ œ๊ณต์—…์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.

{
  "aud": "/projects/project_number/apps/my_project_id",
  "gcip": '{
    "auth_time": 1553219869,
    "email": "demo_user@gmail.com",
    "email_verified": true,
    "firebase": {
      "identities": {
        "email": [
          "demo_user@gmail.com"
        ],
        "saml.myProvider": [
          "demo_user@gmail.com"
        ]
      },
      "sign_in_attributes": {
        "firstname": "John",
        "group": "test group",
        "role": "admin",
        "lastname": "Doe"
      },
      "sign_in_provider": "saml.myProvider",
      "tenant": "my_tenant_id"
    },
    "sub": "gZG0yELPypZElTmAT9I55prjHg63"
  }',
  "email": "securetoken.google.com/my_project_id/my_tenant_id:demo_user@gmail.com",
  "exp": 1553220470,
  "iat": 1553219870,
  "iss": "https://cloud.google.com/iap",
  "sub": "securetoken.google.com/my_project_id/my_tenant_id:gZG0yELPypZElTmAT9I55prjHg63"
}

์œ ํšจํ•œ ์—ญํ• ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œํ•œํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ์ฝ”๋“œ์™€ ์œ ์‚ฌํ•œ ๋…ผ๋ฆฌ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const gcipClaims = JSON.parse(decodedIapJwtClaims.gcip);
if (gcipClaims &&
    gcipClaims.firebase &&
    gcipClaims.firebase.sign_in_attributes &&
    gcipClaims.firebase.sign_in_attribute.role === 'admin') {
  // Allow access to admin restricted resource.
} else {
  // Block access.
}

gcipClaims.gcip.firebase.sign_in_attributes ์ค‘์ฒฉ ํด๋ ˆ์ž„์„ ์‚ฌ์šฉํ•˜์—ฌ Identity Platform SAML ๋ฐ OIDC ์ œ๊ณต์—…์ฒด์˜ ์ถ”๊ฐ€ ์‚ฌ์šฉ์ž ์†์„ฑ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

IdP ํด๋ ˆ์ž„ ํฌ๊ธฐ ์ œํ•œ

์‚ฌ์šฉ์ž๊ฐ€ Identity Platform์œผ๋กœ ๋กœ๊ทธ์ธํ•œ ํ›„ IAP์— ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌ๋˜๋Š” ์Šคํ…Œ์ดํŠธ๋ฆฌ์Šค(Stateless) Identity Platform ID ํ† ํฐ์— ์ถ”๊ฐ€ ์‚ฌ์šฉ์ž ์†์„ฑ์„ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ํ›„ IAP๊ฐ€ ๋™์ผ ํด๋ ˆ์ž„์ด ํฌํ•จ๋œ ์ž์ฒด ์Šคํ…Œ์ดํŠธ๋ฆฌ์Šค(Stateless) ๋ถˆํˆฌ๋ช… ์ฟ ํ‚ค๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค. IAP๋Š” ์ฟ ํ‚ค ์ฝ˜ํ…์ธ ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„œ๋ช…๋œ JWT ํ—ค๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์„ธ์…˜์ด ๋Œ€์šฉ๋Ÿ‰ ํด๋ ˆ์ž„์œผ๋กœ ์‹œ์ž‘๋œ ๊ฒฝ์šฐ ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ผ๋ฐ˜์ ์œผ๋กœ 4KB ์ •๋„์ธ ์ตœ๋Œ€ ํ—ˆ์šฉ ์ฟ ํ‚ค ํฌ๊ธฐ๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋กœ๊ทธ์ธ ์ž‘์—…์ด ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํด๋ ˆ์ž„๋งŒ IdP SAML ๋˜๋Š” OIDC ์†์„ฑ์— ์ „๋‹ฌ๋˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ ๋‹ค๋ฅธ ์˜ต์…˜์€ ์ฐจ๋‹จ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šน์ธ ๊ฒ€์‚ฌ์— ํ•„์š”ํ•˜์ง€ ์•Š์€ ํด๋ ˆ์ž„์„ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const gcipCloudFunctions = require('gcip-cloud-functions');

const authFunctions = new gcipCloudFunctions.Auth().functions();

// This function runs before any sign-in operation.
exports.beforeSignIn = authFunctions.beforeSignInHandler((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider') {
    // Get the original claims.
    const claims = context.credential.claims;
    // Define this function to filter out the unnecessary claims.
    claims.groups = keepNeededClaims(claims.groups);
    // Return only the needed claims. The claims will be propagated to the token
    // payload.
    return {
      sessionClaims: claims,
    };
  }
});