Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PKCE support to AuthorizationCodeFlow #470

Merged
merged 19 commits into from Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
61a616e
Initial test code for a PKCE enabled Authorization Code Flow
StFS May 26, 2020
4bf2925
WIP: work on README.md
StFS May 27, 2020
778797b
Script to initialize keycloak by adding client via REST API.
StFS May 27, 2020
73fa760
Improve keycloak init script and some code cleanup. Still WIP.
StFS May 27, 2020
b0f0a9f
Merge branch 'pkce-support' of github.com:StFS/google-oauth-java-clie…
StFS May 27, 2020
8b5e316
WIP: work on README.md
StFS May 28, 2020
3bb51aa
Working PKCE AuthorizationCodeFlow. Some cleanup of test classes.
StFS May 28, 2020
b582b87
Add scopes back to the AuthorizationCodeRequestUrl creation.
StFS May 28, 2020
7238b21
Simplify code by moving PKCE entirely into the AuthorizationCodeFlow …
StFS May 29, 2020
c5def75
Remove wildcard imports as that seems to be the way to do things here.
StFS May 29, 2020
b089e33
Add @since annotation in JavaDoc to the PKCE parameters of the autori…
StFS May 29, 2020
066cadc
Add PKCE unit test, documentation and minor cleanup of dependencies f…
StFS May 29, 2020
7272beb
Add PKCE unit test, documentation and minor cleanup of dependencies f…
StFS May 29, 2020
bdb43ac
Merge branch 'pkce-support' of github.com:StFS/google-oauth-java-clie…
StFS May 29, 2020
f2efde6
Annotate PKCE with Beta annotation.
StFS May 29, 2020
1f0e9b8
Responding to code review comments
StFS Jun 8, 2020
d3e7b47
Responding to more PR comments
StFS Jun 8, 2020
1ee31a0
Improve Keycloak PKCE sample documentation
StFS Jun 8, 2020
0196cfa
Add license header with copyright to new files. Improve documentation.
StFS Jun 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -194,13 +194,13 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro
* </pre>
*/
public AuthorizationCodeRequestUrl newAuthorizationUrl() {
AuthorizationCodeRequestUrl acru = new AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId);
acru.setScopes(scopes);
AuthorizationCodeRequestUrl url = new AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId);
url.setScopes(scopes);
if (pkce != null) {
acru.setCodeChallenge(pkce.getChallenge());
acru.setCodeChallengeMethod(pkce.getChallengeMethod());
url.setCodeChallenge(pkce.getChallenge());
url.setCodeChallengeMethod(pkce.getChallengeMethod());
}
return acru;
return url;
}

/**
Expand Down Expand Up @@ -447,23 +447,41 @@ public interface CredentialCreatedListener {
*/
private static class PKCE {
private final String verifier;
private final String challenge;
private String challenge;
private String challengeMethod;

public PKCE() {
verifier = generateVerifier();
Challenge c = new Challenge(verifier);
challenge = c.challenge;
challengeMethod = c.method;
generateChallenge(verifier);
}

private String generateVerifier() {
private static String generateVerifier() {
SecureRandom sr = new SecureRandom();
byte[] code = new byte[32];
sr.nextBytes(code);
return Base64.encodeBase64URLSafeString(code);
}

/**
* Create the PKCE code verifier. It will use the S256 method but
StFS marked this conversation as resolved.
Show resolved Hide resolved
* will fall back to using the 'plain' method in the unlikely case
* that the SHA-256 MessageDigest algorithm implementation can't be
* loaded.
*/
private void generateChallenge(String verifier) {
try {
byte[] bytes = verifier.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
challenge = Base64.encodeBase64URLSafeString(digest);
challengeMethod = "S256";
} catch (NoSuchAlgorithmException e) {
challenge = verifier;
challengeMethod = "plain";
}
}

public String getVerifier() {
return verifier;
}
Expand All @@ -475,34 +493,6 @@ public String getChallenge() {
public String getChallengeMethod() {
return challengeMethod;
}

/**
* An abstraction to create the PKCE code verifier. It will use the S256 method but
* will fall back to using the 'plain' method in the unlikely case that the SHA-256
* MessageDigest algorithm implementation can't be loaded.
*/
private static class Challenge {
private String challenge;
private String method;

private Challenge(String verifier) {
try {
challenge = generateChallenge(verifier);
method = "S256";
} catch (NoSuchAlgorithmException e) {
challenge = verifier;
method = "plain";
}
}

private String generateChallenge(String verifier) throws NoSuchAlgorithmException {
byte[] bytes = verifier.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
return Base64.encodeBase64URLSafeString(digest);
}
}
}

/**
Expand Down Expand Up @@ -881,7 +871,7 @@ public Builder setRequestInitializer(HttpRequestInitializer requestInitializer)

/**
* Enables Proof Key for Code Exchange (PKCE) for this Athorization Code Flow.
* @since 1.30.7
* @since 1.31
*/
@Beta
public Builder enablePKCE() {
Expand Down
Expand Up @@ -56,14 +56,14 @@ public class AuthorizationCodeRequestUrl extends AuthorizationRequestUrl {

/**
* The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge</a>.
* @since 1.30.7
* @since 1.31
*/
@Key("code_challenge")
String codeChallenge;

/**
* The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge Method</a>.
* @since 1.30.7
* @since 1.31
*/
@Key("code_challenge_method")
String codeChallengeMethod;
Expand All @@ -79,7 +79,7 @@ public AuthorizationCodeRequestUrl(String authorizationServerEncodedUrl, String
/**
* Get the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
*
* @since 1.30.7
* @since 1.31
*/
public String getCodeChallenge() {
return codeChallenge;
Expand All @@ -88,7 +88,7 @@ public String getCodeChallenge() {
/**
* Get the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
*
* @since 1.30.7
* @since 1.31
*/
public String getCodeChallengeMethod() {
return codeChallengeMethod;
Expand All @@ -98,7 +98,7 @@ public String getCodeChallengeMethod() {
* Set the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
* @param codeChallenge the code challenge.
*
* @since 1.30.7
* @since 1.31
*/
public void setCodeChallenge(String codeChallenge) {
this.codeChallenge = codeChallenge;
Expand All @@ -108,7 +108,7 @@ public void setCodeChallenge(String codeChallenge) {
* Set the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
* @param codeChallengeMethod the code challenge method.
*
* @since 1.30.7
* @since 1.31
*/
public void setCodeChallengeMethod(String codeChallengeMethod) {
this.codeChallengeMethod = codeChallengeMethod;
Expand Down
@@ -1,9 +1,13 @@
package com.google.api.services.samples.keycloak.cmdline;
StFS marked this conversation as resolved.
Show resolved Hide resolved

import com.google.api.client.auth.oauth2.*;
import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.http.*;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
Expand Down