Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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
*
* 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
* <a href="https://tools.ietf.org/html/rfc7523">JSON Web Token (JWT) Profile
* for OAuth 2.0 Client Authentication and Authorization Grants</a>
*
Comment on lines +23 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.io.IOException;
import java.util.Map;
/**
* Client credentials specified as URL-encoded parameters in the HTTP request body as specified in
* <a href="https://tools.ietf.org/html/rfc7523">JSON Web Token (JWT) Profile
* for OAuth 2.0 Client Authentication and Authorization Grants</a>
*
* <a href="https://tools.ietf.org/html/rfc7523">JSON Web Token (JWT) Profile for OAuth 2.0 Client
* Authentication and Authorization Grants</a>
* <p>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 causes an IllegalArgumentException.

* <p>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)}.
*
* <p>
* 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 causes an IllegalArgumentException.
* </p>
*
* <p>Sample usage:
*
* <pre>
* 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());
* }
* }
* }
* </pre>
*
* <p>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";

Comment on lines +74 to +82

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 class JWTAuthentication implements HttpRequestInitializer, HttpExecuteInterceptor {
/** Predefined value for grant_type when using JWT * */
/** 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";
/** @param jwt JWT used for authentication */

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<String, Object> 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new IllegalArgumentException(GRANT_TYPE_KEY
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* 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
*
* 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import com.google.api.client.http.GenericUrl;
import static org.junit.Assert.assertThrows;
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 java.util.Map;
import junit.framework.TestCase;
import static org.junit.Assert.assertThrows;
Comment on lines +24 to +25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import junit.framework.TestCase;
import static org.junit.Assert.assertThrows;
import junit.framework.TestCase;

import org.junit.function.ThrowingRunnable;

/**
* 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 =
Comment on lines +39 to +42

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new ClientCredentialsTokenRequest(new MockHttpTransport(), new JacksonFactory(),
new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString()));
JWTAuthentication auth =
new ClientCredentialsTokenRequest(
new MockHttpTransport(),
new JacksonFactory(),
new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString()));
JWTAuthentication auth = new JWTAuthentication(JWT);

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<String, ?> data = (Map<String, ?>) 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);
Comment on lines +65 to +66

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JWTAuthentication auth =
new JWTAuthentication(JWT);
JWTAuthentication auth = new JWTAuthentication(JWT);

assertEquals(JWT, auth.getJWT());
auth.intercept(request);
UrlEncodedContent content = (UrlEncodedContent) request.getContent();
@SuppressWarnings("unchecked")
Map<String, ?> data = (Map<String, ?>) 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 =
Comment on lines +78 to +81

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new ClientCredentialsTokenRequest(new MockHttpTransport(), new JacksonFactory(),
new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString()));
JWTAuthentication auth =
new ClientCredentialsTokenRequest(
new MockHttpTransport(),
new JacksonFactory(),
new GenericUrl(HttpTesting.SIMPLE_GENERIC_URL.toString()));
JWTAuthentication auth = new JWTAuthentication(JWT);

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() {
Comment on lines +90 to +102

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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() {
assertThrows(
IllegalArgumentException.class,
new ThrowingRunnable() {
@Override
public void run() throws Throwable {
request.executeUnparsed();
}
});
assertThrows(
RuntimeException.class,
new ThrowingRunnable() {
@Override
public void run() {
JWTAuthentication auth = new JWTAuthentication(null);
}
});

JWTAuthentication auth = new JWTAuthentication(null);
}
});
}
}