From 26291449f38c3fcb24886e5a9771da235fefe683 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 28 Dec 2020 19:56:16 -0500 Subject: [PATCH 1/9] Implement JWT OAuth 2 Client Authentication as HttpExecuteInterceptor. --- .../client/auth/oauth2/JWTAuthentication.java | 127 ++++++++++++++++++ .../auth/oauth2/JWTAuthenticationTest.java | 109 +++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java create mode 100644 google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java new file mode 100644 index 000000000..05696e110 --- /dev/null +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Jun Ying. + * + * 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.google.api.client.auth.oauth2; + +import com.google.api.client.http.HttpExecuteInterceptor; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.util.Data; +import com.google.api.client.util.Preconditions; + +import java.io.IOException; +import java.util.Map; + +/** + * Client credentials specified as URL-encoded parameters in the HTTP request body as specified in + * JSON Web Token (JWT) Profile + * for OAuth 2.0 Client Authentication and Authorization Grants + * + *

This implementation assumes that the {@link HttpRequest#getContent()} is {@code null} or an + * instance of {@link UrlEncodedContent}. This is used as the client authentication in {@link + * TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}. + * + *

+ * To use JWT authentication, grant_type must be "client_credentials". + * If AuthorizationCodeTokenRequest.setGrantType() is called, set it to + * JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS. It can also be left + * uncalled. Setting it to any other value will cause an IllegalArgumentException. + *

+ * + *

Sample usage: + * + *

+ * static void requestAccessToken() throws IOException {
+ * try {
+ * TokenResponse response = new AuthorizationCodeTokenRequest(new NetHttpTransport(),
+ * new JacksonFactory(), new GenericUrl("https://server.example.com/token")
+ * .setGrantType(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS)
+ * .setClientAuthentication(
+ * new JWTAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9...")).execute();
+ * System.out.println("Access token: " + response.getAccessToken());
+ * } catch (TokenResponseException e) {
+ * if (e.getDetails() != null) {
+ * System.err.println("Error: " + e.getDetails().getError());
+ * if (e.getDetails().getErrorDescription() != null) {
+ * System.err.println(e.getDetails().getErrorDescription());
+ * }
+ * if (e.getDetails().getErrorUri() != null) {
+ * System.err.println(e.getDetails().getErrorUri());
+ * }
+ * } else {
+ * System.err.println(e.getMessage());
+ * }
+ * }
+ * }
+ * 
+ * + *

Implementation is immutable and thread-safe. + * + * @author Jun Ying + */ + +public class JWTAuthentication + implements HttpRequestInitializer, HttpExecuteInterceptor { + + public static final String GRANT_TYPE_KEY = "grant_type"; + + /** Predefined value for grant_type when using JWT **/ + public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + + public static final String CLIENT_ASSERTION_TYPE_KEY = "client_assertion_type"; + + /** Predefined value for client_assertion_type when using JWT **/ + public static final String CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + + public static final String CLIENT_ASSERTION_KEY = "client_assertion"; + + /** JWT for authentication. */ + private final String jwt; + + /** + * @param jwt JWT used for authentication + */ + public JWTAuthentication(String jwt) { + this.jwt = Preconditions.checkNotNull(jwt); + } + + public void initialize(HttpRequest request) throws IOException { + request.setInterceptor(this); + } + + public void intercept(HttpRequest request) { + Map data = Data.mapOf(UrlEncodedContent.getContent(request).getData()); + if (!data.containsKey(GRANT_TYPE_KEY)) { + data.put(GRANT_TYPE_KEY, GRANT_TYPE_CLIENT_CREDENTIALS); + } else { + String grantType = (String) data.get(GRANT_TYPE_KEY); + if (!grantType.equals(GRANT_TYPE_CLIENT_CREDENTIALS)) { + throw new IllegalArgumentException(GRANT_TYPE_KEY + + " must be " + + GRANT_TYPE_CLIENT_CREDENTIALS + + ", not " + + grantType + + "."); + } + } + data.put(CLIENT_ASSERTION_TYPE_KEY, CLIENT_ASSERTION_TYPE); + data.put(CLIENT_ASSERTION_KEY, jwt); + } + + /** Returns the JWT. */ + public final String getJWT() { + return jwt; + } +} diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java new file mode 100644 index 000000000..9834ca394 --- /dev/null +++ b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2011 Google Inc. + * + * 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.google.api.client.auth.oauth2; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.testing.http.HttpTesting; +import com.google.api.client.testing.http.MockHttpTransport; +import junit.framework.TestCase; +import org.junit.function.ThrowingRunnable; + +import java.util.Map; + +import static org.junit.Assert.assertThrows; + +/** + * Tests {@link JWTAuthentication}. + * + * @author Jun Ying + */ +public class JWTAuthenticationTest extends TestCase { + + private static final String JWT = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9"; + + public void test() throws Exception { + TokenRequest request = + new ClientCredentialsTokenRequest(new MockHttpTransport(), new JacksonFactory(), + new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString())); + + JWTAuthentication auth = + new JWTAuthentication(JWT); + + assertEquals(JWT, auth.getJWT()); + + request.setGrantType(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS); + + request.setClientAuthentication(auth); + + HttpRequest httpRequest = request.executeUnparsed().getRequest(); + auth.intercept(httpRequest); + UrlEncodedContent content = (UrlEncodedContent) httpRequest.getContent(); + @SuppressWarnings("unchecked") + Map data = (Map) content.getData(); + assertEquals(JWT, data.get("client_assertion")); + assertEquals(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS, data.get("grant_type")); + } + + public void testNoGrantType() throws Exception { + HttpRequest request = + new MockHttpTransport() + .createRequestFactory() + .buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL); + JWTAuthentication auth = + new JWTAuthentication(JWT); + assertEquals(JWT, auth.getJWT()); + auth.intercept(request); + UrlEncodedContent content = (UrlEncodedContent) request.getContent(); + @SuppressWarnings("unchecked") + Map data = (Map) content.getData(); + assertEquals(JWT, data.get("client_assertion")); + assertEquals(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS, data.get("grant_type")); + } + + public void testInvalidGrantType() { + final TokenRequest request = + new ClientCredentialsTokenRequest(new MockHttpTransport(), new JacksonFactory(), + new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString())); + + JWTAuthentication auth = + new JWTAuthentication(JWT); + + assertEquals(JWT, auth.getJWT()); + + request.setGrantType("invalid"); + + request.setClientAuthentication(auth); + + + assertThrows(IllegalArgumentException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + request.executeUnparsed(); + } + }); + } + + public void test_noJWT() { + assertThrows(RuntimeException.class, new ThrowingRunnable() { + @Override + public void run() { + JWTAuthentication auth = new JWTAuthentication(null); + } + }); + } +} From a5979bad86436a1e2d5b2643662c17ca488d6ac1 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Tue, 29 Dec 2020 09:37:44 -0500 Subject: [PATCH 2/9] Assigned copyrigh to Google. --- .../com/google/api/client/auth/oauth2/JWTAuthentication.java | 2 +- .../google/api/client/auth/oauth2/JWTAuthenticationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index 05696e110..3af865123 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Jun Ying. + * Copyright (c) 2020 Google Inc. * * 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 diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java index 9834ca394..3c3dabf49 100644 --- a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java +++ b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Google Inc. + * Copyright (c) 2020 Google Inc. * * 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 From 8cad037916f2fa3a77d331c1fc84bc4ab626b485 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Tue, 29 Dec 2020 09:41:22 -0500 Subject: [PATCH 3/9] Updated comment to present tense per Google style. --- .../com/google/api/client/auth/oauth2/JWTAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index 3af865123..b0b506fe4 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -37,7 +37,7 @@ * To use JWT authentication, grant_type must be "client_credentials". * If AuthorizationCodeTokenRequest.setGrantType() is called, set it to * JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS. It can also be left - * uncalled. Setting it to any other value will cause an IllegalArgumentException. + * uncalled. Setting it to any other value causes an IllegalArgumentException. *

* *

Sample usage: From 122be494adbd8de173f0a61721bea553910818a4 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Tue, 29 Dec 2020 09:46:56 -0500 Subject: [PATCH 4/9] Changed code usage example in comment to use GsonFactory per Google's request. --- .../com/google/api/client/auth/oauth2/JWTAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index b0b506fe4..5329f5259 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -46,7 +46,7 @@ * static void requestAccessToken() throws IOException { * try { * TokenResponse response = new AuthorizationCodeTokenRequest(new NetHttpTransport(), - * new JacksonFactory(), new GenericUrl("https://server.example.com/token") + * new GsonFactory(), new GenericUrl("https://server.example.com/token") * .setGrantType(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS) * .setClientAuthentication( * new JWTAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9...")).execute(); From 00b2c5801fad28ed7c5791cae5f09e39fc50c31d Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 11 Jan 2021 08:48:32 -0500 Subject: [PATCH 5/9] Took out (c) in copyright. --- .../google/api/client/auth/oauth2/JWTAuthenticationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java index 3c3dabf49..7b47e8197 100644 --- a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java +++ b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Google Inc. + * Copyright 2020 Google Inc. * * 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 From 3d3dfc021ef150f1781bb6af4d1c0a465a0a7eb5 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 11 Jan 2021 08:56:37 -0500 Subject: [PATCH 6/9] Took out (c) in copyright. --- .../com/google/api/client/auth/oauth2/JWTAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index 5329f5259..20a381bd9 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Google Inc. + * Copyright 2020 Google Inc. * * 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 From 02c5093cfdf3cce7d8edeba54c518f14c822d345 Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 11 Jan 2021 08:57:56 -0500 Subject: [PATCH 7/9] Sorted imports. --- .../api/client/auth/oauth2/JWTAuthenticationTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java index 7b47e8197..d9ef6272e 100644 --- a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java +++ b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/JWTAuthenticationTest.java @@ -20,12 +20,10 @@ import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.testing.http.HttpTesting; import com.google.api.client.testing.http.MockHttpTransport; -import junit.framework.TestCase; -import org.junit.function.ThrowingRunnable; - import java.util.Map; - +import junit.framework.TestCase; import static org.junit.Assert.assertThrows; +import org.junit.function.ThrowingRunnable; /** * Tests {@link JWTAuthentication}. From ed06dcd42ecfb193b38422e583cdaeedcffdf19a Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 11 Jan 2021 09:05:50 -0500 Subject: [PATCH 8/9] Changed indent to 2 spaces per Google coding style standard. --- .../client/auth/oauth2/JWTAuthentication.java | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index 20a381bd9..03a0745dd 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -73,55 +73,55 @@ */ public class JWTAuthentication - implements HttpRequestInitializer, HttpExecuteInterceptor { + implements HttpRequestInitializer, HttpExecuteInterceptor { - public static final String GRANT_TYPE_KEY = "grant_type"; + public static final String GRANT_TYPE_KEY = "grant_type"; - /** Predefined value for grant_type when using JWT **/ - public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + /** Predefined value for grant_type when using JWT **/ + public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; - public static final String CLIENT_ASSERTION_TYPE_KEY = "client_assertion_type"; + public static final String CLIENT_ASSERTION_TYPE_KEY = "client_assertion_type"; - /** Predefined value for client_assertion_type when using JWT **/ - public static final String CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + /** Predefined value for client_assertion_type when using JWT **/ + public static final String CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - public static final String CLIENT_ASSERTION_KEY = "client_assertion"; + public static final String CLIENT_ASSERTION_KEY = "client_assertion"; - /** JWT for authentication. */ - private final String jwt; + /** JWT for authentication. */ + private final String jwt; - /** - * @param jwt JWT used for authentication - */ - public JWTAuthentication(String jwt) { - this.jwt = Preconditions.checkNotNull(jwt); - } + /** + * @param jwt JWT used for authentication + */ + public JWTAuthentication(String jwt) { + this.jwt = Preconditions.checkNotNull(jwt); + } - public void initialize(HttpRequest request) throws IOException { - request.setInterceptor(this); - } + public void initialize(HttpRequest request) throws IOException { + request.setInterceptor(this); + } - public void intercept(HttpRequest request) { - Map data = Data.mapOf(UrlEncodedContent.getContent(request).getData()); - if (!data.containsKey(GRANT_TYPE_KEY)) { - data.put(GRANT_TYPE_KEY, GRANT_TYPE_CLIENT_CREDENTIALS); - } else { - String grantType = (String) data.get(GRANT_TYPE_KEY); - if (!grantType.equals(GRANT_TYPE_CLIENT_CREDENTIALS)) { - throw new IllegalArgumentException(GRANT_TYPE_KEY - + " must be " - + GRANT_TYPE_CLIENT_CREDENTIALS - + ", not " - + grantType - + "."); - } - } - data.put(CLIENT_ASSERTION_TYPE_KEY, CLIENT_ASSERTION_TYPE); - data.put(CLIENT_ASSERTION_KEY, jwt); + public void intercept(HttpRequest request) { + Map data = Data.mapOf(UrlEncodedContent.getContent(request).getData()); + if (!data.containsKey(GRANT_TYPE_KEY)) { + data.put(GRANT_TYPE_KEY, GRANT_TYPE_CLIENT_CREDENTIALS); + } else { + String grantType = (String) data.get(GRANT_TYPE_KEY); + if (!grantType.equals(GRANT_TYPE_CLIENT_CREDENTIALS)) { + throw new IllegalArgumentException(GRANT_TYPE_KEY + + " must be " + + GRANT_TYPE_CLIENT_CREDENTIALS + + ", not " + + grantType + + "."); + } } + data.put(CLIENT_ASSERTION_TYPE_KEY, CLIENT_ASSERTION_TYPE); + data.put(CLIENT_ASSERTION_KEY, jwt); + } - /** Returns the JWT. */ - public final String getJWT() { - return jwt; - } + /** Returns the JWT. */ + public final String getJWT() { + return jwt; + } } From 53cb1207da4cb51aa601815353fbd7d1b0fcb6cb Mon Sep 17 00:00:00 2001 From: Jun Ying Date: Mon, 11 Jan 2021 09:07:58 -0500 Subject: [PATCH 9/9] Indents for sample code in comment. --- .../client/auth/oauth2/JWTAuthentication.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java index 03a0745dd..344004cf3 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/JWTAuthentication.java @@ -44,26 +44,26 @@ * *

  * static void requestAccessToken() throws IOException {
- * try {
- * TokenResponse response = new AuthorizationCodeTokenRequest(new NetHttpTransport(),
- * new GsonFactory(), new GenericUrl("https://server.example.com/token")
- * .setGrantType(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS)
- * .setClientAuthentication(
- * new JWTAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9...")).execute();
- * System.out.println("Access token: " + response.getAccessToken());
- * } catch (TokenResponseException e) {
- * if (e.getDetails() != null) {
- * System.err.println("Error: " + e.getDetails().getError());
- * if (e.getDetails().getErrorDescription() != null) {
- * System.err.println(e.getDetails().getErrorDescription());
- * }
- * if (e.getDetails().getErrorUri() != null) {
- * System.err.println(e.getDetails().getErrorUri());
- * }
- * } else {
- * System.err.println(e.getMessage());
- * }
- * }
+ *   try {
+ *     TokenResponse response = new AuthorizationCodeTokenRequest(new NetHttpTransport(),
+ *       new GsonFactory(), new GenericUrl("https://server.example.com/token")
+ *       .setGrantType(JWTAuthentication.GRANT_TYPE_CLIENT_CREDENTIALS)
+ *       .setClientAuthentication(
+ *     new JWTAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9...")).execute();
+ *     System.out.println("Access token: " + response.getAccessToken());
+ *   } catch (TokenResponseException e) {
+ *     if (e.getDetails() != null) {
+ *         System.err.println("Error: " + e.getDetails().getError());
+ *       if (e.getDetails().getErrorDescription() != null) {
+ *         System.err.println(e.getDetails().getErrorDescription());
+ *       }
+ *       if (e.getDetails().getErrorUri() != null) {
+ *         System.err.println(e.getDetails().getErrorUri());
+ *       }
+ *     } else {
+ *       System.err.println(e.getMessage());
+ *     }
+ *   }
  * }
  * 
*