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

OpenID Connect Authentication #14

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ private UserType validateSuppliedPassword (String username, String password, Has
classname = "edu.harvard.i2b2.pm.util.SecurityAuthenticationNTLM";
else if (param.get("authentication_method").equals("LDAP"))
classname = "edu.harvard.i2b2.pm.util.SecurityAuthenticationLDAP";
else if (param.get("authentication_method").equals("OIDC"))
classname = "edu.harvard.i2b2.pm.util.SecurityAuthenticationOIDC";

ClassLoader classLoader = ServicesHandler.class.getClassLoader();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Copyright 2017 - 2018 The Hyve B.V.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package edu.harvard.i2b2.pm.util;

import com.auth0.jwk.*;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.Hashtable;

/*
* OpenID Connect authentication for i2b2 v1.7.10 (might work with other versions, not tested).
* Supports JWT verification signed with RS256 and keys retrieved via JWKS.
* Token is passed through the password field of the XML.
*
* The parameters are listed below with their possible values in ():
* authentication_method - (OIDC)
* oidc_jwks_uri - () URI of JWKS to retrieve the public signing keys
* oidc_client_id - () client ID of this i2b2 instance
* oidc_user_field - () claim field containing the username
* oidc_token_issuer - () token issuer
*
* Some JWT fields are checked: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
* - audience must match the client ID
* - username must match i2b2 username
* - nonce check is made by the caller
*/
public class SecurityAuthenticationOIDC implements SecurityAuthentication {

private static Log log = LogFactory.getLog(SecurityAuthenticationOIDC.class);

private static JwkProvider jwkProvider = null;
private static String jwksUri = "";
private static JwkProvider getJwkProvider(String pJwksUri) throws MalformedURLException {
if (!jwksUri.equals(pJwksUri) || jwkProvider == null) {
jwksUri = pJwksUri;
jwkProvider = new GuavaCachedJwkProvider(new UrlJwkProvider(new URL(jwksUri)));
}
return jwkProvider;
}

/**
* @param username username provided via the i2b2 API headers
* @param tokenString JWT provided via the i2b2 API headers password field
* @param params i2b2 parameters containing OIDC configuration
*
* @return true if authentication valid
* @throws Exception if authentication not valid or any kind of error
*/
@Override
public boolean validateUser(String username, String tokenString, Hashtable params) throws Exception {

try {

String pJwksUri = (String) params.get("oidc_jwks_uri"),
pClientId = (String) params.get("oidc_client_id"),
pUserField = (String) params.get("oidc_user_field"),
pTokenIssuer = (String) params.get("oidc_token_issuer");

log.debug("validateUser() with jwks URI:" + pJwksUri);

DecodedJWT jwt = com.auth0.jwt.JWT.decode(tokenString);
Jwk jwk = getJwkProvider(pJwksUri).get(jwt.getKeyId());
RSAPublicKey signingPubKey = (RSAPublicKey) jwk.getPublicKey();

// verif algorithm and signing key
if (signingPubKey == null || !jwk.getAlgorithm().equals("RS256")) {
throw new Exception("Rejected authentication: Problematic public key = " + signingPubKey + " or algo = " + jwk.getAlgorithm());
}

// token validation
com.auth0.jwt.JWT
.require(Algorithm.RSA256(signingPubKey, null))
.withIssuer(pTokenIssuer) // check issuer
.withAudience(pClientId) // check audience matches
.withClaim(pUserField, username) // check username matches i2b2 internal username
.build()
.verify(tokenString); // check time validity and signature

} catch (Exception e) {
log.warn("Failed authentication: " + e.getMessage());
throw new Exception("Token is invalid, please request a new one: " + e.getMessage(), e);
}

log.debug("validateUser() validation successful");
return true;
}
}
1 change: 1 addition & 0 deletions edu.harvard.i2b2.server-common/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<fileset dir="${lib}/jdbc" includes="**/*" />
<fileset dir="${lib}/commons" includes="**/*" />
<fileset dir="${lib}/jboss" includes="**/*" />
<fileset dir="${lib}/oidc" includes="**/*" />
</copy>
</target>

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.