diff --git a/run/service-auth/pom.xml b/run/service-auth/pom.xml
new file mode 100644
index 00000000000..103c2685413
--- /dev/null
+++ b/run/service-auth/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+ 4.0.0
+ com.example.run
+ service-auth
+ 0.0.1-SNAPSHOT
+ jar
+
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+ UTF-8
+ UTF-8
+ 17
+ 17
+ 3.2.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ junit
+ junit
+ test
+
+
+ com.google.api-client
+ google-api-client
+ 2.7.2
+
+
+ com.google.http-client
+ google-http-client
+ 1.47.0
+
+
+ com.google.auth
+ google-auth-library-oauth2-http
+ 1.35.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+
+
+ repackage
+
+
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ 3.4.0
+
+
+ gcr.io/PROJECT_ID/service-auth
+
+
+
+
+
+
\ No newline at end of file
diff --git a/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java b/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java
new file mode 100644
index 00000000000..9c288484e31
--- /dev/null
+++ b/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.serviceauth;
+
+// [START cloudrun_service_to_service_receive]
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
+import com.google.api.client.http.apache.v2.ApacheHttpTransport;
+import com.google.api.client.json.gson.GsonFactory;
+import java.util.Arrays;
+import java.util.Collection;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RestController;
+
+@SpringBootApplication
+public class Authentication {
+ @RestController
+ @CrossOrigin(exposedHeaders = "*", allowedHeaders = "*")
+ class AuthenticationController {
+
+ @Autowired private AuthenticationService authService;
+
+ @GetMapping("/")
+ public ResponseEntity getEmailFromAuthHeader(
+ @RequestHeader("X-Serverless-Authorization") String authHeader) {
+ String responseBody;
+ if (authHeader == null) {
+ responseBody = "Error verifying ID token: missing X-Serverless-Authorization header";
+ return new ResponseEntity<>(responseBody, HttpStatus.UNAUTHORIZED);
+ }
+
+ String email = authService.parseAuthHeader(authHeader);
+ if (email == null) {
+ responseBody = "Unauthorized request. Please supply a valid bearer token.";
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("WWW-Authenticate", "Bearer");
+ return new ResponseEntity<>(responseBody, headers, HttpStatus.UNAUTHORIZED);
+ }
+
+ responseBody = "Hello, " + email;
+ return new ResponseEntity<>(responseBody, HttpStatus.OK);
+ }
+ }
+
+ @Service
+ public class AuthenticationService {
+ /*
+ * Parse the authorization header, validate and decode the Bearer token.
+ *
+ * Args:
+ * authHeader: String of HTTP header with a Bearer token.
+ *
+ * Returns:
+ * A string containing the email from the token.
+ * null if the token is invalid or the email can't be retrieved.
+ */
+ public String parseAuthHeader(String authHeader) {
+ // Split the auth type and value from the header.
+ String[] authHeaderStrings = authHeader.split(" ");
+ if (authHeaderStrings.length != 2) {
+ System.out.println("Malformed Authorization header");
+ return null;
+ }
+ String authType = authHeaderStrings[0];
+ String tokenValue = authHeaderStrings[1];
+
+ // Get the service URL from the environment variable
+ // set at the time of deployment.
+ String serviceUrl = System.getenv("SERVICE_URL");
+ // Define the expected audience as the Service Base URL.
+ Collection audience = Arrays.asList(serviceUrl);
+
+ // Validate and decode the ID token in the header.
+ if ("Bearer".equals(authType)) {
+ try {
+ // Find more information about the verification process in:
+ // https://developers.google.com/identity/sign-in/web/backend-auth#java
+ // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier
+ GoogleIdTokenVerifier verifier =
+ new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), new GsonFactory())
+ .setAudience(audience)
+ .build();
+ GoogleIdToken googleIdToken = verifier.verify(tokenValue);
+
+ if (googleIdToken != null) {
+ // More info about the structure for the decoded ID Token here:
+ // https://cloud.google.com/docs/authentication/token-types#id
+ // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken
+ // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload
+ GoogleIdToken.Payload payload = googleIdToken.getPayload();
+ if (payload.getEmailVerified()) {
+ return payload.getEmail();
+ }
+ System.out.println("Invalid token. Email wasn't verified.");
+ }
+ } catch (Exception exception) {
+ System.out.println("Ivalid token: " + exception);
+ }
+ } else {
+ System.out.println("Unhandled header format: " + authType);
+ }
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(Authentication.class, args);
+ }
+
+ // [END cloudrun_service_to_service_receive]
+}
diff --git a/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java b/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java
new file mode 100644
index 00000000000..36494aa3ee4
--- /dev/null
+++ b/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.serviceauth;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.IdTokenCredentials;
+import com.google.auth.oauth2.IdTokenProvider;
+import com.google.auth.oauth2.IdTokenProvider.Option;
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class AuthenticationTests {
+
+ private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT");
+ private static String REGION = "us-central1";
+ private String projectNumber;
+ private String serviceUrl;
+ private String serviceName;
+ private HttpClient httpClient;
+
+ @BeforeEach
+ public void setUp() {
+ this.projectNumber = getProjectNumber();
+ this.serviceName = generateServiceName();
+ this.serviceUrl = generateServiceUrl();
+ this.deployService();
+
+ this.httpClient = HttpClient.newHttpClient();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ this.deleteService();
+ }
+
+ private String getProjectNumber() {
+ return getOutputFromCommand(
+ List.of("gcloud", "projects", "describe", PROJECT_ID, "--format=value(projectNumber)"));
+ }
+
+ private String generateServiceName() {
+ return String.format("receive-java-%s", UUID.randomUUID().toString().substring(0, 8));
+ }
+
+ private String generateServiceUrl() {
+ return String.format("https://%s-%s.%s.run.app", this.serviceName, this.projectNumber, REGION);
+ }
+
+ private String deployService() {
+ return getOutputFromCommand(
+ List.of(
+ "gcloud",
+ "run",
+ "deploy",
+ serviceName,
+ "--project",
+ PROJECT_ID,
+ "--source",
+ ".",
+ "--region=" + REGION,
+ "--allow-unauthenticated",
+ "--set-env-vars=SERVICE_URL=" + serviceUrl,
+ "--quiet"));
+ }
+
+ private String deleteService() {
+ return getOutputFromCommand(
+ List.of(
+ "gcloud",
+ "run",
+ "services",
+ "delete",
+ serviceName,
+ "--project",
+ PROJECT_ID,
+ "--async",
+ "--region=" + REGION,
+ "--quiet"));
+ }
+
+ private String getOutputFromCommand(List command) {
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+
+ Process process = processBuilder.start();
+ String output =
+ new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).strip();
+
+ process.waitFor();
+
+ return output;
+ } catch (InterruptedException | IOException exception) {
+ return String.format("Exception: %s", exception);
+ }
+ }
+
+ private String getGoogleIdToken() {
+ try {
+ GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault();
+
+ IdTokenCredentials idTokenCredentials =
+ IdTokenCredentials.newBuilder()
+ .setIdTokenProvider((IdTokenProvider) googleCredentials)
+ .setTargetAudience(serviceUrl)
+ .setOptions(Arrays.asList(Option.FORMAT_FULL, Option.LICENSES_TRUE))
+ .build();
+
+ return idTokenCredentials.refreshAccessToken().getTokenValue();
+ } catch (IOException exception) {
+ return "error_generating_token";
+ }
+ }
+
+ private HttpResponse executeRequest(String headerName, String headerValue) {
+ HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(URI.create(serviceUrl)).GET();
+ if (headerName != null) {
+ requestBuilder = requestBuilder.header(headerName, headerValue);
+ }
+ HttpRequest request = requestBuilder.build();
+ HttpResponse response = null;
+ int retryDelay = 2000;
+ int retryLimit = 3;
+
+ for (int attempt = 0; attempt < retryLimit; attempt++) {
+ try {
+ response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() == HttpStatusCodes.STATUS_CODE_OK
+ || response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {
+ return response;
+ }
+ } catch (HttpTimeoutException exception) {
+ System.out.println(String.format("TimeoutException: %s", exception));
+ System.out.println("Retrying...");
+ } catch (IOException | InterruptedException exception) {
+ System.out.println(String.format("Exception: %s", exception));
+ System.out.println("Retrying...");
+ }
+
+ try {
+ Thread.sleep(retryDelay);
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ @Test
+ public void testValidToken() throws Exception {
+ String token = getGoogleIdToken();
+ HttpResponse response = executeRequest("X-Serverless-Authorization", "Bearer " + token);
+
+ assertTrue(response != null);
+ assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_OK);
+ assertTrue(response.body().contains("Hello,"));
+ }
+
+ @Test
+ public void testInvalidToken() throws Exception {
+ String token = "invalid_token";
+ HttpResponse response = executeRequest("X-Serverless-Authorization", "Bearer " + token);
+
+ assertTrue(response != null);
+ assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
+ assertTrue(response.body().contains("Please supply a valid bearer token."));
+ }
+
+ @Test
+ public void testAnonymousRequest() throws Exception {
+ HttpResponse response = executeRequest(null, null);
+
+ assertTrue(response != null);
+ assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
+ assertTrue(response.body().contains("missing X-Serverless-Authorization header"));
+ }
+}