Skip to content

Commit

Permalink
Enable PKCE in OIDC federated flow
Browse files Browse the repository at this point in the history
  • Loading branch information
lasanthaS committed Mar 30, 2023
1 parent 0283f37 commit 2f08df5
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ private OIDCAuthenticatorConstants() {
public static final String LOGOUT_TOKEN = "logout_token";
public static final Pattern OIDC_BACKCHANNEL_LOGOUT_ENDPOINT_URL_PATTERN = Pattern.compile("(.*)/identity/oidc" +
"/slo(.*)");

public static final String OAUTH_FEDERATED_PKCE_CODE_VERIFIER = "OAUTH_PKCE_CODE_VERIFIER";
public static final String ENABLE_FEDERATED_PKCE = "IsPKCEEnabled";

public class AuthenticatorConfParams {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -371,6 +374,8 @@ protected void initiateAuthenticationRequest(HttpServletRequest request, HttpSer
String authorizationEP = getOIDCAuthzEndpoint(authenticatorProperties);
String callbackurl = getCallbackUrl(authenticatorProperties);
String state = getStateParameter(context, authenticatorProperties);
boolean isPKCEEnabled = Boolean.parseBoolean(
authenticatorProperties.get(OIDCAuthenticatorConstants.ENABLE_FEDERATED_PKCE));

OAuthClientRequest authzRequest;

Expand Down Expand Up @@ -427,6 +432,18 @@ protected void initiateAuthenticationRequest(HttpServletRequest request, HttpSer
loginPage = loginPage + "&fidp=" + domain;
}

// If PKCE is enabled, add code_challenge and code_challenge_method to the request.
if (isPKCEEnabled) {
String codeVerifier = generateCodeVerifier();
context.setProperty(OIDCAuthenticatorConstants.OAUTH_FEDERATED_PKCE_CODE_VERIFIER, codeVerifier);
try {
String codeChallenge = generateCodeChallenge(codeVerifier);
loginPage += "&code_challenge=" + codeChallenge + "&code_challenge_method=S256";
} catch (NoSuchAlgorithmException e) {
log.error("Error while generating the code challenge", e);
}
}

if (StringUtils.isNotBlank(queryString)) {
if (!queryString.startsWith("&")) {
loginPage = loginPage + "&" + queryString;
Expand Down Expand Up @@ -795,6 +812,9 @@ protected OAuthClientRequest getAccessTokenRequest(AuthenticationContext context
String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
String clientSecret = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_SECRET);
String tokenEndPoint = getTokenEndpoint(authenticatorProperties);
boolean isPKCEEnabled = Boolean.parseBoolean(
authenticatorProperties.get(OIDCAuthenticatorConstants.ENABLE_FEDERATED_PKCE));
Object codeVerifier = context.getProperty(OIDCAuthenticatorConstants.OAUTH_FEDERATED_PKCE_CODE_VERIFIER);

String callbackUrl = getCallbackUrlFromInitialRequestParamMap(context);
if (StringUtils.isBlank(callbackUrl)) {
Expand All @@ -813,9 +833,21 @@ protected OAuthClientRequest getAccessTokenRequest(AuthenticationContext context
"authentication scheme.");
}

accessTokenRequest = OAuthClientRequest.tokenLocation(tokenEndPoint).setGrantType(GrantType
.AUTHORIZATION_CODE).setRedirectURI(callbackUrl).setCode(authzResponse.getCode())
.buildBodyMessage();
OAuthClientRequest.TokenRequestBuilder tokenRequestBuilder = OAuthClientRequest
.tokenLocation(tokenEndPoint)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setRedirectURI(callbackUrl)
.setCode(authzResponse.getCode());

if (isPKCEEnabled) {
if (codeVerifier != null) {
tokenRequestBuilder.setParameter("code_verifier", codeVerifier.toString());
} else {
log.warn("PKCE is enabled, but the code verifier is not found.");
}
}

accessTokenRequest = tokenRequestBuilder.buildBodyMessage();
String base64EncodedCredential = new String(Base64.encodeBase64((clientId + ":" +
clientSecret).getBytes()));
accessTokenRequest.addHeader(OAuth.HeaderType.AUTHORIZATION, "Basic " + base64EncodedCredential);
Expand All @@ -825,10 +857,23 @@ protected OAuthClientRequest getAccessTokenRequest(AuthenticationContext context
LOG.debug("Authenticating to token endpoint: " + tokenEndPoint + " including client credentials "
+ "in request body.");
}
accessTokenRequest = OAuthClientRequest.tokenLocation(tokenEndPoint).setGrantType(GrantType
.AUTHORIZATION_CODE).setClientId(clientId).setClientSecret(clientSecret).setRedirectURI
(callbackUrl).setCode(authzResponse.getCode()).buildBodyMessage();
OAuthClientRequest.TokenRequestBuilder tokenRequestBuilder = OAuthClientRequest
.tokenLocation(tokenEndPoint)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId(clientId)
.setClientSecret(clientSecret)
.setRedirectURI(callbackUrl)
.setCode(authzResponse.getCode());
if (isPKCEEnabled) {
if (codeVerifier != null) {
tokenRequestBuilder.setParameter("code_verifier", codeVerifier.toString());
} else {
log.warn("PKCE is enabled, but the code verifier is not found.");
}
}
accessTokenRequest = tokenRequestBuilder.buildBodyMessage();
}
context.removeProperty(OIDCAuthenticatorConstants.OAUTH_FEDERATED_PKCE_CODE_VERIFIER);
// set 'Origin' header to access token request.
if (accessTokenRequest != null) {
// fetch the 'Hostname' configured in carbon.xml
Expand All @@ -844,7 +889,6 @@ protected OAuthClientRequest getAccessTokenRequest(AuthenticationContext context
} catch (URLBuilderException e) {
throw new RuntimeException("Error occurred while building URL in tenant qualified mode.", e);
}

return accessTokenRequest;
}

Expand Down Expand Up @@ -1008,6 +1052,15 @@ public List<Property> getConfigurationProperties() {
enableBasicAuth.setDisplayOrder(10);
configProperties.add(enableBasicAuth);

Property enablePKCE = new Property();
enablePKCE.setName("isPKCEEnabled");
enablePKCE.setDisplayName("Enable PKCE");
enablePKCE.setRequired(false);
enablePKCE.setDescription("Specifies that PKCE should be used for client authentication");
enablePKCE.setType("boolean");
enablePKCE.setDisplayOrder(10);
configProperties.add(enablePKCE);

return configProperties;
}

Expand Down Expand Up @@ -1260,4 +1313,33 @@ private String getCallbackUrlFromInitialRequestParamMap(AuthenticationContext co

return null;
}

/**
* Generate code verifier for PKCE
*
* @return code verifier
*/
private String generateCodeVerifier() {
SecureRandom secureRandom = new SecureRandom();
byte[] codeVerifier = new byte[32];
secureRandom.nextBytes(codeVerifier);
return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier);
}

/**
* Generate code challenge for PKCE
*
* @param codeVerifier code verifier
* @return code challenge
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private String generateCodeChallenge(String codeVerifier)
throws UnsupportedEncodingException, NoSuchAlgorithmException {
byte[] bytes = codeVerifier.getBytes("US-ASCII");
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(bytes, 0, bytes.length);
byte[] digest = messageDigest.digest();
return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
}
}

0 comments on commit 2f08df5

Please sign in to comment.