Securing your app with signed headers Stay organized with collections Save and categorize content based on your preferences.
This page describes how to secure your app with signed IAPheaders. When configured, Identity-Aware Proxy (IAP) uses JSONWeb Tokens (JWT) to make sure that a request to your app isauthorized. This protects your app from the following risks:
- IAP is accidentally disabled
- Misconfigured firewalls
- Unauthorized access from within the project
To help secure your app, you must use signed headers for all app types.
Alternatively, if you have an App Engine standard environment app, youcan use theUsers API.
Compute Engine and GKE health checksdon't include JWT headers and IAP doesn't process healthchecks. If your health check returns access errors, make sure that you have the health check configured correctly in the Google Cloud console and thatyour JWT header validation allows the health check path. For moreinformation, seeCreate a health check exception.
Before you begin
To secure your app with signed headers, you'll need the following:
- An application that you want users to connect to.
- Athird-party JWT library for your language that supportsthe
ES256algorithm.
Securing your app with IAP headers
To secure your app with the IAP JWT, verify the header,payload, and signature of the JWT. The JWT is in the HTTP request headerx-goog-iap-jwt-assertion. If an attacker bypasses IAP, theattacker can forge the IAP unsigned identity headers,x-goog-authenticated-user-{email,id}. The IAP JWT providesa more secure alternative.
Signed headers provide secondary security in case someone bypassesIAP. When IAP is enabled, IAPstrips thex-goog-* headers provided by the client when the request goesthrough the IAP serving infrastructure.
Verifying the JWT header
Verify that the JWT's header conforms to the following constraints:
| JWT Header Claims | ||
|---|---|---|
alg | Algorithm | ES256 |
kid | Key ID | Must correspond to one of the public keys listed in the IAP key file, available in two different formats:https://www.gstatic.com/iap/verify/public_key andhttps://www.gstatic.com/iap/verify/public_key-jwk |
Ensure that the JWT was signed by the private key that corresponds tothe token'skid claim. First, retrieve the public key from one of two places:
https://www.gstatic.com/iap/verify/public_key. This URL contains a JSONdictionary that maps thekidclaims to the public key values.https://www.gstatic.com/iap/verify/public_key-jwk. This URL containsthe IAP public keys inJWK format.
After you have the public key, use a JWT library to verify the signature.
IAP periodically rotates its public keys. To make sure that you can always verify the JWTs, seeAutomate public key caching.
Verifying the JWT payload
Verify the JWT's payload conforms to the following constraints:
| JWT Payload Claims | ||
|---|---|---|
exp | Expiration time | Must be in the future. The time is measured in seconds since the UNIX epoch. Allow 30 seconds for skew.The maximum lifetime of a token is 10 minutes + 2 * skew. |
iat | Issued-at time | Must be in the past. The time is measured in seconds since the UNIX epoch. Allow 30 seconds for skew. |
aud | Audience | Must be a string with the following values:
|
iss | Issuer | Must behttps://cloud.google.com/iap. |
hd | Account domain | If an account belongs to a hosted domain, thehd claim is provided to differentiate the domain the account is associated with. |
google | Google claim | If one or moreaccess levels apply to the request, their names are stored within thegoogle claim's JSON object, under theaccess_levels key, as an array of strings.When you specify a device policy and the Org has access to the device data, the |
You can get the values for theaud string mentioned above by accessing theGoogle Cloud console, or you can use the gcloud command-line tool.
To getaud string values from the Google Cloud console, go to theIdentity-Aware Proxy settingsfor your project, clickMore next to the Load Balancer resource, and thenselectSigned Header JWT Audience. TheSigned Header JWT dialog thatappears shows theaud claim for the selected resource.
If you want to use thegcloud CLIgcloud command-line tool to get theaud string values, you'll need to knowthe project ID. You can find the project ID on theGoogle Cloud consoleProject info card, then run the specified commands for each value.
Project number
To get your project number using the gcloud command-line tool, run the following command:
gcloud projects describePROJECT_ID
The command returns output like the following:
createTime:'2016-10-13T16:44:28.170Z'lifecycleState:ACTIVEname:project_nameparent:id:'433637338589'type:organizationprojectId:PROJECT_IDprojectNumber:'PROJECT_NUMBER'
Service ID
To get your service ID using the gcloud command-line tool, run the following command:
gcloud compute backend-services describeSERVICE_NAME --project=PROJECT_ID --global
The command returns output like the following:
affinityCookieTtlSec:0backends:-balancingMode:UTILIZATIONcapacityScaler:1.0group:https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/instanceGroups/my-groupconnectionDraining:drainingTimeoutSec:0creationTimestamp:'2017-04-03T14:01:35.687-07:00'description:''enableCDN:falsefingerprint:zaOnO4k56Cw=healthChecks:-https://www.googleapis.com/compute/v1/projects/project_name/global/httpsHealthChecks/my-hcid:'SERVICE_ID'kind:compute#backendServiceloadBalancingScheme:EXTERNALname:my-serviceport:8443portName:httpsprotocol:HTTPSselfLink:https://www.googleapis.com/compute/v1/projects/project_name/global/backendServices/my-servicesessionAffinity:NONEtimeoutSec:3610
Retrieving the user identity
If all of the preceding verifications are successful, retrieve the user identity.The ID token's payload contains the following user information:
| ID Token Payload User Identity | ||
|---|---|---|
sub | Subject | The unique, stable identifier for the user. Use this value instead of thex-goog-authenticated-user-id header. |
email | User email | User email address.
|
Following is sample code to secure an app with signed IAPheaders:
C#
usingGoogle.Apis.Auth;usingGoogle.Apis.Auth.OAuth2;usingSystem;usingSystem.Threading;usingSystem.Threading.Tasks;publicclassIAPTokenVerification{/// <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>publicasyncTask<JsonWebSignature.Payload>VerifyTokenAsync(stringsignedJwt,stringexpectedAudience,CancellationTokencancellationToken=default){SignedTokenVerificationOptionsoptions=newSignedTokenVerificationOptions{// 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,};returnawaitJsonWebSignature.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.funcvalidateJWTFromAppEngine(wio.Writer,iapJWT,projectNumber,projectIDstring)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)iferr!=nil{returnfmt.Errorf("idtoken.Validate: %w",err)}// payload contains the JWT claims for further inspection or validationfmt.Fprintf(w,"payload: %v",payload)returnnil}// validateJWTFromComputeEngine validates a JWT found in the// "x-goog-iap-jwt-assertion" header.funcvalidateJWTFromComputeEngine(wio.Writer,iapJWT,projectNumber,backendServiceIDstring)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)iferr!=nil{returnfmt.Errorf("idtoken.Validate: %w",err)}// payload contains the JWT claims for further inspection or validationfmt.Fprintf(w,"payload: %v",payload)returnnil}Java
importcom.google.api.client.http.HttpRequest;importcom.google.api.client.json.webtoken.JsonWebToken;importcom.google.auth.oauth2.TokenVerifier;/** Verify IAP authorization JWT token in incoming request. */publicclassVerifyIapRequestHeader{privatestaticfinalStringIAP_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.booleanverifyJwtForAppEngine(HttpRequestrequest,longprojectNumber,StringprojectId)throwsException{// Check for iap jwt header in incoming requestStringjwt=request.getHeaders().getFirstHeaderStringValue("x-goog-iap-jwt-assertion");if(jwt==null){returnfalse;}returnverifyJwt(jwt,String.format("/projects/%s/apps/%s",Long.toUnsignedString(projectNumber),projectId));}booleanverifyJwtForComputeEngine(HttpRequestrequest,longprojectNumber,longbackendServiceId)throwsException{// Check for iap jwt header in incoming requestStringjwtToken=request.getHeaders().getFirstHeaderStringValue("x-goog-iap-jwt-assertion");if(jwtToken==null){returnfalse;}returnverifyJwt(jwtToken,String.format("/projects/%s/global/backendServices/%s",Long.toUnsignedString(projectNumber),Long.toUnsignedString(backendServiceId)));}privatebooleanverifyJwt(StringjwtToken,StringexpectedAudience){TokenVerifiertokenVerifier=TokenVerifier.newBuilder().setAudience(expectedAudience).setIssuer(IAP_ISSUER_URL).build();try{JsonWebTokenjsonWebToken=tokenVerifier.verify(jwtToken);// Verify that the token contain subject and email claimsJsonWebToken.Payloadpayload=jsonWebToken.getPayload();returnpayload.getSubject()!=null &&payload.get("email")!=null;}catch(TokenVerifier.VerificationExceptione){System.out.println(e.getMessage());returnfalse;}}}Node.js
/** * TODO(developer): Uncomment these variables before running the sample. */// const iapJwt = 'SOME_ID_TOKEN'; // JWT from the "x-goog-iap-jwt-assertion" headerletexpectedAudience=null;if(projectNumber &&projectId){// Expected Audience for App Engine.expectedAudience=`/projects/${projectNumber}/apps/${projectId}`;}elseif(projectNumber &&backendServiceId){// Expected Audience for Compute EngineexpectedAudience=`/projects/${projectNumber}/global/backendServices/${backendServiceId}`;}constoAuth2Client=newOAuth2Client();asyncfunctionverify(){// Verify the id_token, and access the claims.constresponse=awaitoAuth2Client.getIapPublicKeys();constticket=awaitoAuth2Client.verifySignedJwtWithCertsAsync(iapJwt,response.pubkeys,expectedAudience,['https://cloud.google.com/iap'],);// Print out the info contained in the IAP ID tokenconsole.log(ticket);}verify().catch(console.error);PHP
namespace Google\Cloud\Samples\Iap;# Imports Google auth libraries for IAP validationuse 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
fromgoogle.auth.transportimportrequestsfromgoogle.oauth2importid_tokendefvalidate_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"],"")exceptExceptionase: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=nilifproject_number &&project_id# Expected audience for App Engineaudience="/projects/#{project_number}/apps/#{project_id}"elsifproject_number &&backend_service_id# Expected audience for Compute Engineaudience="/projects/#{project_number}/global/backendServices/#{backend_service_id}"end# The client ID as the target audience for IAPpayload=Google::Auth::IDTokens.verify_iapiap_jwt,aud:audienceputspayloadifaudience.nil?puts"Audience not verified! Supply a project_number and project_id to verify"endTesting your validation code
If you visit your app using thesecure_token_test query parameters,IAP will include an invalid JWT. Use this to make sure yourJWT-validation logic is handling all of the various failure cases and to seehow your app behaves when it receives an invalid JWT.
Creating a health check exception
As mentioned previously, Compute Engine and GKEhealth checks don't use JWT headers and IAP doesn't handlehealth checks. You'll need to configure yourhealth check and app to allow thehealth check access.
Configuring the health check
If you haven't already set a path for your health check, use theGoogle Cloud console to set a non-sensitive path for the health check. Makesure this path isn't shared by any other resource.
- Go to the Google Cloud consoleHealth checks page.
Go to the Health checks page - Click the health check you're using for your app, then clickEdit.
- UnderRequest path add a non-sensitive path name. This specifies the URL path that Google Cloud uses when sending health check requests. If omitted, the health check request is sent to
/. - ClickSave.
Configuring the JWT validation
In your code that calls the JWT validation routine, add a condition to serve an200 HTTP status for your health check request path. For example:
if HttpRequest.path_info = '/HEALTH_CHECK_REQUEST_PATH' return HttpResponse(status=200)elseVALIDATION_FUNCTION
Automate public key caching
IAP rotates its public keys periodically. To ensure you canalways verify the IAP JWT, we recommend that youcache the keys to avoid fetching them from the public URL for each requestand that you automate the process of updating the cached key. This approachis particularly useful for applications that run in an environment with network restrictions, like a VPC Service Controls perimeter.
A VPC Service Controls perimeter can prevent direct access to the public URLfor the keys. By caching the keys in a Cloud Storage bucket, yourapplications can fetch them from a location within your VPC-SC perimeter.
The following Terraform configuration deploys a function toCloud Run that fetches the latest IAP public keys fromhttps://www.gstatic.com/iap/verify/public_key-jwk and stores them in aCloud Storage bucket. A Cloud Scheduler job triggers this function every12 hours to keep the keys up to date.
This setup includes the following:
- Necessary Google Cloud APIs enabled to use Cloud Run and store and cache keys
- A Cloud Storage bucket to store the fetched IAP public keys
- A Cloud Storage bucket to stage Cloud Run functions source code
- Service accounts for Cloud Run functions and Cloud Scheduler with appropriate IAM permissions
- A Python function to fetch and store keys
- A Cloud Scheduler job to trigger the function every 12 hours
Directory structure
├── function_source/│ ├── main.py│ └── requirements.txt├── main.tf├── outputs.tf├── variables.tf└── terraform.tfvars
function_source/main.py
importfunctions_frameworkimportrequestsfromgoogle.cloudimportstorageimportos# Environment variables to be set in the function configurationBUCKET_NAME=os.environ.get("BUCKET_NAME")OBJECT_NAME=os.environ.get("OBJECT_NAME","iap_public_keys.jwk")IAP_KEYS_URL="https://www.gstatic.com/iap/verify/public_key-jwk"@functions_framework.httpdefupdate_iap_keys(request):"""Fetches IAP public keys from the public URL and stores them in a Cloud Storage bucket."""ifnotBUCKET_NAME:print("Error: BUCKET_NAME environment variable not set.")return"BUCKET_NAME environment variable not set.",500try:# Fetch the keysresponse=requests.get(IAP_KEYS_URL)response.raise_for_status()# Raise an exception for bad status codeskeys_content=response.textprint(f"Successfully fetched keys from{IAP_KEYS_URL}")# Store in Cloud Storagestorage_client=storage.Client()bucket=storage_client.bucket(BUCKET_NAME)blob=bucket.blob(OBJECT_NAME)blob.upload_from_string(keys_content,content_type='application/json')print(f"Successfully wrote IAP keys to gs://{BUCKET_NAME}/{OBJECT_NAME}")returnf"Successfully updated{OBJECT_NAME} in bucket{BUCKET_NAME}",200exceptrequests.exceptions.RequestExceptionase:print(f"Error fetching keys from{IAP_KEYS_URL}:{e}")returnf"Error fetching keys:{e}",500exceptExceptionase:print(f"Error interacting with Cloud Storage:{e}")returnf"Error interacting with Cloud Storage:{e}",500
Replace the following:
BUCKET_NAME: the name of your Cloud Storage bucketOBJECT_NAME: the name of the object to store your keys to
function_source/requirements.txt
functions-framework==3.*requestsgoogle-cloud-storage
variables.tf
variable"project_id"{description="The Google Cloud project ID."type=stringdefault=PROJECT_ID}variable"region"{description="The Google Cloud region."type=stringdefault="REGION"}variable"iap_keys_bucket_name"{description="The name of the Cloud Storage bucket to store IAP keys."type=stringdefault=BUCKET_NAME"}variable"function_source_bucket_name"{description="The name of the Cloud Storage bucket to store the function source code."type=stringdefault="BUCKET_NAME_FUNCTION"}
Replace the following:
PROJECT_ID: your Google Cloud project IDREGION: the region to deploy resources in—for example,us-central1BUCKET_NAME: the name for the Cloud Storage bucket that stores IAP keysBUCKET_NAME_FUNCTION: the name for the Cloud Storage bucket that stores Cloud Run functions source code
main.tf
terraform{required_providers{google={source="hashicorp/google"version=">= 4.50.0"}google-beta={source="hashicorp/google-beta"version=">= 4.50.0"}}}provider"google"{project=var.project_idregion=var.region}provider"google-beta"{project=var.project_idregion=var.region}# Enable necessary APIsresource"google_project_service""services"{for_each=toset(["storage.googleapis.com","cloudfunctions.googleapis.com","run.googleapis.com", # Cloud Functions v2 uses Cloud Run"cloudscheduler.googleapis.com","iamcredentials.googleapis.com","cloudbuild.googleapis.com" # Needed for Cloud Functions deployment])service=each.keydisable_on_destroy=false}# Cloud Storage Bucket to store the IAP public keysresource"google_storage_bucket""iap_keys_bucket"{name=var.iap_keys_bucket_namelocation=var.regionuniform_bucket_level_access=trueversioning{enabled=true}lifecycle{prevent_destroy=false # Set to true in production to prevent accidental deletion}}# Cloud Storage Bucket to store the Cloud Function source coderesource"google_storage_bucket""function_source_bucket"{name=var.function_source_bucket_namelocation=var.regionuniform_bucket_level_access=true}# Archive the function source codedata"archive_file""function_source_zip"{type="zip"source_dir="${path.module}/function_source"output_path="${path.module}/function_source.zip"}# Upload the zipped source code to the source bucketresource"google_storage_bucket_object""function_source_object"{name="function_source.zip"bucket=google_storage_bucket.function_source_bucket.namesource=data.archive_file.function_source_zip.output_path}# Service Account for the Cloud Functionresource"google_service_account""iap_key_updater_sa"{account_id="iap-key-updater"display_name="IAP Key Updater Function SA"}# Grant the function's SA permission to write to the IAP keys bucketresource"google_storage_bucket_iam_member""keys_bucket_writer"{bucket=google_storage_bucket.iap_keys_bucket.namerole="roles/storage.objectAdmin"member="serviceAccount:${google_service_account.iap_key_updater_sa.email}"}# Cloud Function (v2)resource"google_cloudfunctions2_function""update_iap_keys_func"{provider=google-beta # CFv2 often has newer features in google-betaname="update-iap-keys-function"location=var.regionbuild_config{runtime="python312"entry_point="update_iap_keys"source{storage_source{bucket=google_storage_bucket.function_source_bucket.nameobject=google_storage_bucket_object.function_source_object.name}}}service_config{max_instance_count=1available_memory="256M"timeout_seconds=60ingress_settings="ALLOW_ALL"service_account_email=google_service_account.iap_key_updater_sa.emailenvironment_variables={BUCKET_NAME=google_storage_bucket.iap_keys_bucket.nameOBJECT_NAME="iap_public_keys.jwk"}}depends_on=[google_project_service.services,google_storage_bucket_iam_member.keys_bucket_writer]}# Service Account for the Cloud Scheduler jobresource"google_service_account""iap_key_scheduler_sa"{account_id="iap-key-scheduler"display_name="IAP Key Update Scheduler SA"}# Grant the Scheduler SA permission to invoke the Cloud Functionresource"google_cloudfunctions2_function_iam_member""invoker"{provider=google-betaproject=google_cloudfunctions2_function.update_iap_keys_func.projectlocation=google_cloudfunctions2_function.update_iap_keys_func.locationcloud_function=google_cloudfunctions2_function.update_iap_keys_func.namerole="roles/cloudfunctions.invoker"member="serviceAccount:${google_service_account.iap_key_scheduler_sa.email}"}# Cloud Scheduler Jobresource"google_cloud_scheduler_job""iap_key_update_schedule"{name="iap-key-update-schedule"description="Fetches IAP public keys and stores them in Cloud Storage every 12 hours"schedule="0 */12 * * *" # Every 12 hourstime_zone="Etc/UTC"region=var.regionhttp_target{uri=google_cloudfunctions2_function.update_iap_keys_func.service_config[0].urihttp_method="POST"oidc_token{service_account_email=google_service_account.iap_key_scheduler_sa.email}}depends_on=[google_cloudfunctions2_function_iam_member.invoker,google_project_service.services]}
outputs.tf
output"iap_keys_bucket_url"{description="The Cloud Storage bucket URL where IAP public keys are stored."value="gs://${google_storage_bucket.iap_keys_bucket.name}"}output"cloud_function_url"{description="The URL of the Cloud Function endpoint that triggers key updates."value=google_cloudfunctions2_function.update_iap_keys_func.service_config[0].uri}
terraform.tfvars
Create aterraform.tfvars file to specify your project ID and customizebucket names if needed:
project_id="your-gcp-project-id"# Optional: Customize bucket names# iap_keys_bucket_name = "custom-iap-keys-bucket"# function_source_bucket_name = "custom-func-src-bucket"
Deploy with Terraform
- Save the files in the directory structure described previously.
- Navigate to the directory in your terminal and initialize Terraform:
terraforminit
- Plan the changes:
terraformplan
- Apply the changes:
terraformapply
This deploys the infrastructure. The Cloud Scheduler job triggers thefunction every 12 hours, fetching the IAP keys and storing themings://BUCKET_NAME/iap_public_keys.jwk by default. Yourapplications can now fetch the keys from this bucket.
Clean up resources
To remove the resources created by Terraform, run the following commands:
gsutilrm-ags://BUCKET_NAME/**terraformdestroy-auto-approve
ReplaceBUCKET_NAME with the Cloud Storage bucket for your keys.
gsutil command is necessary because Terraform doesn't destroyCloud Storage buckets that contain objects when versioning is enabled.JWTs for external identities
If you're using IAP with external identities,IAP will still issue a signed JWT on every authenticatedrequest, just as it does with Google identities. However, there are a fewdifferences.
Provider information
When using external identities, the JWT payload will contain a claimnamedgcip. This claim contains user information, such as their email, photoURL, and any additional provider-specific attributes.
The following is an example of a JWT for a user who logged in with Facebook:
"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"}',Theemail andsub fields
If a user was authenticated by Identity Platform, theemail andsubfields of the JWT will be prefixed with the Identity Platform token issuerand the tenant ID used (if any). For example:
"email":"securetoken.google.com/PROJECT-ID/TENANT-ID:demo_user@gmail.com","sub":"securetoken.google.com/PROJECT-ID/TENANT-ID:gZG0yELPypZElTmAT9I55prjHg63"
Controlling access withsign_in_attributes
IAM doesn't support external identities, but you can use claimsembedded in thesign_in_attributes field to control access. For example,consider a user signed in using a SAML provider:
{"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"}You could add logic to your application similar to the code below to restrictaccess to users with a valid role:
constgcipClaims=JSON.parse(decodedIapJwtClaims.gcip);if(gcipClaims&&gcipClaims.firebase&&gcipClaims.firebase.sign_in_attributes&&gcipClaims.firebase.sign_in_attribute.role==='admin'){//Allowaccesstoadminrestrictedresource.}else{//Blockaccess.}You can access additional user attributes from Identity Platform SAML andOIDC providers using thegcipClaims.gcip.firebase.sign_in_attributes nestedclaim.
IdP claims size limitations
After a user signs in with Identity Platform, the additional user attributeswill be propagated to the stateless Identity Platform ID token payload, whichwill be securely passed to IAP. IAP willthen issue its own stateless opaque cookie, which also contains the same claims.IAP will generate the signed JWT header based on the cookiecontent.
As a result, if a session is initiated with many claims, it mightexceed the maximum allowed cookie size, which is typically about 4KB in most browsers.This will cause the sign-in operation to fail.
Make sure that only the necessary claims are propagated in the IdP SAMLor OIDC attributes. Another option is to useblocking functions to filter outthe claims that aren't required for the authorization check.
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, }; }});Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-18 UTC.