checkpoint on oauth for static site

- store state + redirectUri in a table
- redirect again to get code & state out of query string
- add meta-data validation to oauth2 module
This commit is contained in:
2025-03-24 09:25:53 -05:00
parent f99c39e0f6
commit 410175a133
9 changed files with 369 additions and 58 deletions

View File

@ -646,6 +646,8 @@ public class QInstanceValidator
validateSimpleCodeReference("Instance Authentication meta data customizer ", authentication.getCustomizer(), QAuthenticationModuleCustomizerInterface.class);
}
authentication.validate(qInstance, this);
runPlugins(QAuthenticationMetaData.class, authentication, qInstance);
}
}

View File

@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.model.metadata.authentication;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.OAuth2AuthenticationModule;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@ -37,6 +40,9 @@ public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
private String tokenUrl;
private String clientId;
private String userSessionTableName;
private String redirectStateTableName;
////////////////////////////////////////////////////////////////////////////////////////
// keep this secret, on the server - don't let it be serialized and sent to a client! //
////////////////////////////////////////////////////////////////////////////////////////
@ -61,6 +67,33 @@ public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
/***************************************************************************
**
***************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
{
super.validate(qInstance, qInstanceValidator);
String prefix = "OAuth2AuthenticationMetaData (named '" + getName() + "'): ";
qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), prefix + "baseUrl must be set");
qInstanceValidator.assertCondition(StringUtils.hasContent(clientId), prefix + "clientId must be set");
qInstanceValidator.assertCondition(StringUtils.hasContent(clientSecret), prefix + "clientSecret must be set");
if(qInstanceValidator.assertCondition(StringUtils.hasContent(userSessionTableName), prefix + "userSessionTableName must be set"))
{
qInstanceValidator.assertCondition(qInstance.getTable(userSessionTableName) != null, prefix + "userSessionTableName ('" + userSessionTableName + "') was not found in the instance");
}
if(qInstanceValidator.assertCondition(StringUtils.hasContent(redirectStateTableName), prefix + "redirectStateTableName must be set"))
{
qInstanceValidator.assertCondition(qInstance.getTable(redirectStateTableName) != null, prefix + "redirectStateTableName ('" + redirectStateTableName + "') was not found in the instance");
}
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
@ -189,4 +222,66 @@ public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
return (this);
}
/*******************************************************************************
** Getter for userSessionTableName
*******************************************************************************/
public String getUserSessionTableName()
{
return (this.userSessionTableName);
}
/*******************************************************************************
** Setter for userSessionTableName
*******************************************************************************/
public void setUserSessionTableName(String userSessionTableName)
{
this.userSessionTableName = userSessionTableName;
}
/*******************************************************************************
** Fluent setter for userSessionTableName
*******************************************************************************/
public OAuth2AuthenticationMetaData withUserSessionTableName(String userSessionTableName)
{
this.userSessionTableName = userSessionTableName;
return (this);
}
/*******************************************************************************
** Getter for redirectStateTableName
*******************************************************************************/
public String getRedirectStateTableName()
{
return (this.redirectStateTableName);
}
/*******************************************************************************
** Setter for redirectStateTableName
*******************************************************************************/
public void setRedirectStateTableName(String redirectStateTableName)
{
this.redirectStateTableName = redirectStateTableName;
}
/*******************************************************************************
** Fluent setter for redirectStateTableName
*******************************************************************************/
public OAuth2AuthenticationMetaData withRedirectStateTableName(String redirectStateTableName)
{
this.redirectStateTableName = redirectStateTableName;
return (this);
}
}

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.authentication;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
@ -225,4 +226,15 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
return (this);
}
/***************************************************************************
**
***************************************************************************/
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
{
//////////////////
// noop at base //
//////////////////
}
}

View File

@ -84,7 +84,7 @@ public interface QAuthenticationModuleInterface
/***************************************************************************
**
***************************************************************************/
default String getLoginRedirectUrl(String originalUrl)
default String getLoginRedirectUrl(String originalUrl) throws QAuthenticationException
{
throw (new NotImplementedException("The method getLoginRedirectUrl() is not implemented in the authentication module: " + this.getClass().getSimpleName()));
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URLEncoder;
@ -33,7 +34,7 @@ import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
@ -48,16 +49,20 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.OAuth2AuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.model.UserSession;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
import com.nimbusds.oauth2.sdk.AuthorizationCode;
import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.GeneralException;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.TokenRequest;
import com.nimbusds.oauth2.sdk.TokenResponse;
@ -65,8 +70,11 @@ import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.id.State;
import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import org.json.JSONObject;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -84,8 +92,8 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
.withTimeout(Duration.of(1, ChronoUnit.MINUTES))
.withMaxSize(1000);
// todo wip
private static Map<String, String> stateToRedirectUrl = new HashMap<>();
private static final Memoization<String, OIDCProviderMetadata> oidcProviderMetadataMemoization = new Memoization<String, OIDCProviderMetadata>()
.withMayStoreNullValues(false);
@ -101,36 +109,42 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
if(context.containsKey("code") && context.containsKey("state"))
{
///////////////////////////////////////////////////////////////////////
// handle a callback to initially auth a user for a traditional //
// (non-js) site - where the code & state params come to the backend //
///////////////////////////////////////////////////////////////////////
AuthorizationCode code = new AuthorizationCode(context.get("code"));
// todo - maybe this comes from lookup of state?
URI redirectURI = new URI(stateToRedirectUrl.get(context.get("state")));
/////////////////////////////////////////
// verify the state in our state table //
/////////////////////////////////////////
AtomicReference<String> redirectUri = new AtomicReference<>(null);
QContext.withTemporaryContext(new CapturedContext(qInstance, new QSystemUserSession()), () ->
{
QRecord redirectStateRecord = GetAction.execute(oauth2MetaData.getRedirectStateTableName(), Map.of("state", context.get("state")));
if(redirectStateRecord == null)
{
throw (new QAuthenticationException("State not found"));
}
redirectUri.set(redirectStateRecord.getValueString("redirectUri"));
});
URI redirectURI = new URI(redirectUri.get());
ClientSecretBasic clientSecretBasic = new ClientSecretBasic(new ClientID(oauth2MetaData.getClientId()), new Secret(oauth2MetaData.getClientSecret()));
AuthorizationCodeGrant codeGrant = new AuthorizationCodeGrant(code, redirectURI);
URI tokenEndpoint = new URI(oauth2MetaData.getTokenUrl());
TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientSecretBasic, codeGrant);
TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send());
URI tokenEndpoint = getOIDCProviderMetadata(oauth2MetaData).getTokenEndpointURI();
Scope scope = new Scope("openid profile email offline_access");
TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientSecretBasic, codeGrant, scope);
if(tokenResponse.indicatesSuccess())
{
AccessToken accessToken = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
// todo - what?? RefreshToken refreshToken = tokenResponse.toSuccessResponse().getTokens().getRefreshToken();
QSession session = createSessionFromToken(accessToken.getValue());
insertUserSession(accessToken.getValue(), session);
return (session);
}
else
{
ErrorObject errorObject = tokenResponse.toErrorResponse().getErrorObject();
LOG.info("Token request failed", logPair("code", errorObject.getCode()), logPair("description", errorObject.getDescription()));
throw (new QAuthenticationException(errorObject.getDescription()));
}
return createSessionFromTokenRequest(tokenRequest);
}
else if(context.containsKey("code") && context.containsKey("redirectUri") && context.containsKey("codeVerifier"))
{
////////////////////////////////////////////////////////////////////////////////
// handle a call down to this backend code to initially auth a user for an //
// SPA that received a code (where the javascript generated the codeVerifier) //
////////////////////////////////////////////////////////////////////////////////
AuthorizationCode code = new AuthorizationCode(context.get("code"));
URI callback = new URI(context.get("redirectUri"));
CodeVerifier codeVerifier = new CodeVerifier(context.get("codeVerifier"));
@ -140,30 +154,18 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
Secret clientSecret = new Secret(oauth2MetaData.getClientSecret());
ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);
URI tokenEndpoint = new URI(oauth2MetaData.getTokenUrl());
URI tokenEndpoint = getOIDCProviderMetadata(oauth2MetaData).getTokenEndpointURI();
Scope scope = new Scope("openid profile email offline_access");
TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientAuth, codeGrant, scope);
TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send());
if(tokenResponse.indicatesSuccess())
{
AccessToken accessToken = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
// todo - what?? RefreshToken refreshToken = tokenResponse.toSuccessResponse().getTokens().getRefreshToken();
QSession session = createSessionFromToken(accessToken.getValue());
insertUserSession(accessToken.getValue(), session);
return (session);
}
else
{
ErrorObject errorObject = tokenResponse.toErrorResponse().getErrorObject();
LOG.info("Token request failed", logPair("code", errorObject.getCode()), logPair("description", errorObject.getDescription()));
throw (new QAuthenticationException(errorObject.getDescription()));
}
return createSessionFromTokenRequest(tokenRequest);
}
else if(context.containsKey("sessionUUID") || context.containsKey("sessionId") || context.containsKey("uuid"))
{
//////////////////////////////////////////////////////////////////////
// handle a "normal" request, where we aren't opening a new session //
// per-se, but instead are looking for one in our userSession table //
//////////////////////////////////////////////////////////////////////
String uuid = Objects.requireNonNullElseGet(context.get("sessionUUID"), () ->
Objects.requireNonNullElseGet(context.get("sessionId"), () ->
context.get("uuid")));
@ -171,7 +173,11 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
String accessToken = getAccessTokenFromSessionUUID(uuid);
QSession session = createSessionFromToken(accessToken);
session.setUuid(uuid);
// todo - validate its age or against provider??
//////////////////////////////////////////////////////////////////
// todo - do we need to validate its age or ping the provider?? //
//////////////////////////////////////////////////////////////////
return (session);
}
else
@ -193,6 +199,36 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
/***************************************************************************
**
***************************************************************************/
private QSession createSessionFromTokenRequest(TokenRequest tokenRequest) throws ParseException, IOException, QException
{
TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send());
if(tokenResponse.indicatesSuccess())
{
AccessToken accessToken = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
////////////////////////////////////////////////////////////////////
// todo - do we want to try to do anything with a refresh token?? //
////////////////////////////////////////////////////////////////////
// RefreshToken refreshToken = tokenResponse.toSuccessResponse().getTokens().getRefreshToken();
QSession session = createSessionFromToken(accessToken.getValue());
insertUserSession(accessToken.getValue(), session);
return (session);
}
else
{
ErrorObject errorObject = tokenResponse.toErrorResponse().getErrorObject();
LOG.info("Token request failed", logPair("code", errorObject.getCode()), logPair("description", errorObject.getDescription()));
throw (new QAuthenticationException(errorObject.getDescription()));
}
}
/***************************************************************************
**
***************************************************************************/
@ -228,23 +264,54 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
**
***************************************************************************/
@Override
public String getLoginRedirectUrl(String originalUrl)
public String getLoginRedirectUrl(String originalUrl) throws QAuthenticationException
{
QInstance qInstance = QContext.getQInstance();
OAuth2AuthenticationMetaData oauth2MetaData = (OAuth2AuthenticationMetaData) qInstance.getAuthentication();
try
{
QInstance qInstance = QContext.getQInstance();
OAuth2AuthenticationMetaData oauth2MetaData = (OAuth2AuthenticationMetaData) qInstance.getAuthentication();
String authUrl = getOIDCProviderMetadata(oauth2MetaData).getAuthorizationEndpointURI().toString();
// todo wip - get from meta-data or from that thing that knows the other things?
String authUrl = oauth2MetaData.getTokenUrl().replace("token", "authorize");
QTableMetaData stateTable = QContext.getQInstance().getTable(oauth2MetaData.getRedirectStateTableName());
if(stateTable == null)
{
throw (new QAuthenticationException("The table specified as the oauthRedirectStateTableName [" + oauth2MetaData.getRedirectStateTableName() + "] is not defined in the QInstance"));
}
String state = UUID.randomUUID().toString();
stateToRedirectUrl.put(state, originalUrl);
///////////////////////////////////////////////////////////////////
// generate a secure state, of either default length (32 bytes), //
// or at a size (base64 encoded) that fits in the state table //
///////////////////////////////////////////////////////////////////
Integer stateStringLength = stateTable.getField("state").getMaxLength();
State state = stateStringLength == null ? new State(32) : new State((stateStringLength / 4) * 3);
String stateValue = state.getValue();
return authUrl +
"?client_id=" + URLEncoder.encode(oauth2MetaData.getClientId(), StandardCharsets.UTF_8) +
"&redirect_uri=" + URLEncoder.encode(originalUrl, StandardCharsets.UTF_8) +
"&response_type=code" +
"&scope=" + URLEncoder.encode("openid profile email", StandardCharsets.UTF_8) +
"&state=" + URLEncoder.encode(state, StandardCharsets.UTF_8);
/////////////////////////////
// insert the state record //
/////////////////////////////
QContext.withTemporaryContext(new CapturedContext(qInstance, new QSystemUserSession()), () ->
{
QRecord insertedState = new InsertAction().execute(new InsertInput(oauth2MetaData.getRedirectStateTableName()).withRecord(new QRecord()
.withValue("state", stateValue)
.withValue("redirectUri", originalUrl))).getRecords().get(0);
if(CollectionUtils.nullSafeHasContents(insertedState.getErrors()))
{
throw (new QAuthenticationException("Error storing redirect state: " + insertedState.getErrorsAsString()));
}
});
return authUrl +
"?client_id=" + URLEncoder.encode(oauth2MetaData.getClientId(), StandardCharsets.UTF_8) +
"&redirect_uri=" + URLEncoder.encode(originalUrl, StandardCharsets.UTF_8) +
"&response_type=code" +
"&scope=" + URLEncoder.encode("openid profile email", StandardCharsets.UTF_8) +
"&state=" + URLEncoder.encode(state.getValue(), StandardCharsets.UTF_8);
}
catch(Exception e)
{
LOG.warn("Error getting login redirect url", e);
throw (new QAuthenticationException("Error getting login redirect url", e));
}
}
@ -401,4 +468,19 @@ public class OAuth2AuthenticationModule implements QAuthenticationModuleInterfac
return (true);
}
/***************************************************************************
**
***************************************************************************/
private OIDCProviderMetadata getOIDCProviderMetadata(OAuth2AuthenticationMetaData oAuth2AuthenticationMetaData) throws GeneralException, IOException
{
return oidcProviderMetadataMemoization.getResult(oAuth2AuthenticationMetaData.getName(), (name ->
{
Issuer issuer = new Issuer(oAuth2AuthenticationMetaData.getBaseUrl());
OIDCProviderMetadata metadata = OIDCProviderMetadata.resolve(issuer);
return (metadata);
})).orElseThrow(() -> new GeneralException("Could not resolve OIDCProviderMetadata for " + oAuth2AuthenticationMetaData.getName()));
}
}

View File

@ -0,0 +1,82 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.modules.authentication.implementations.metadata;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
/*******************************************************************************
** Meta Data Producer for RedirectState table
*******************************************************************************/
public class RedirectStateMetaDataProducer extends MetaDataProducer<QTableMetaData>
{
public static final String TABLE_NAME = "redirectState";
private final String backendName;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public RedirectStateMetaDataProducer(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QTableMetaData produce(QInstance qInstance) throws QException
{
QTableMetaData tableMetaData = new QTableMetaData()
.withName(TABLE_NAME)
.withBackendName(backendName)
.withRecordLabelFormat("%s")
.withRecordLabelFields("state")
.withPrimaryKeyField("id")
.withUniqueKey(new UniqueKey("state"))
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("state", QFieldType.STRING).withIsEditable(false).withMaxLength(45).withBehavior(ValueTooLongBehavior.ERROR))
.withField(new QFieldMetaData("redirectUri", QFieldType.STRING).withIsEditable(false).withMaxLength(4096).withBehavior(ValueTooLongBehavior.ERROR))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false));
return tableMetaData;
}
}

View File

@ -137,6 +137,7 @@ import com.kingsrook.qqq.middleware.javalin.misc.DownloadFileSupplementalAction;
import io.javalin.Javalin;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.Context;
import io.javalin.http.Cookie;
import io.javalin.http.UploadedFile;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.http.HttpStatus;
@ -535,14 +536,24 @@ public class QJavalinImplementation
try
{
///////////////////////////////////////////////
// note: duplicated in ExecutorSessionUtils //
///////////////////////////////////////////////
Map<String, String> authenticationContext = new HashMap<>();
String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME);
String sessionUuidCookieValue = context.cookie(Auth0AuthenticationModule.SESSION_UUID_KEY);
String authorizationHeaderValue = context.header("Authorization");
String apiKeyHeaderValue = context.header("x-api-key");
String codeQueryParamValue = context.queryParam("code");
String stateQueryParamValue = context.queryParam("state");
if(StringUtils.hasContent(sessionIdCookieValue))
if(StringUtils.hasContent(codeQueryParamValue) && StringUtils.hasContent(stateQueryParamValue))
{
authenticationContext.put("code", codeQueryParamValue);
authenticationContext.put("state", stateQueryParamValue);
}
else if(StringUtils.hasContent(sessionIdCookieValue))
{
///////////////////////////////////////////////////////
// sessionId - maybe used by table-based auth module //

View File

@ -60,14 +60,24 @@ public class ExecutorSessionUtils
try
{
/////////////////////////////////////////////////
// note: duplicated in QJavalinImplementation //
/////////////////////////////////////////////////
Map<String, String> authenticationContext = new HashMap<>();
String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME);
String sessionUuidCookieValue = context.cookie(Auth0AuthenticationModule.SESSION_UUID_KEY);
String authorizationHeaderValue = context.header("Authorization");
String apiKeyHeaderValue = context.header("x-api-key");
String codeQueryParamValue = context.queryParam("code");
String stateQueryParamValue = context.queryParam("state");
if(StringUtils.hasContent(sessionIdCookieValue))
if(StringUtils.hasContent(codeQueryParamValue) && StringUtils.hasContent(stateQueryParamValue))
{
authenticationContext.put("code", codeQueryParamValue);
authenticationContext.put("state", stateQueryParamValue);
}
else if(StringUtils.hasContent(sessionIdCookieValue))
{
///////////////////////////////////////////////////////
// sessionId - maybe used by table-based auth module //

View File

@ -55,6 +55,23 @@ public class SimpleRouteAuthenticator implements RouteAuthenticatorInterface
{
QSession qSession = QJavalinImplementation.setupSession(context, null);
LOG.debug("Session has been activated", logPair("uuid", qSession.getUuid()));
if(context.queryParamMap().containsKey("code") && context.queryParamMap().containsKey("state"))
{
//////////////////////////////////////////////////////////////////////////
// if this request was a callback from oauth, with code & state params, //
// then redirect one last time removing those from the query string //
//////////////////////////////////////////////////////////////////////////
String redirectURL = context.fullUrl().replace("code=" + context.queryParam("code"), "")
.replace("state=" + context.queryParam("state"), "")
.replaceFirst("&+$", "")
.replaceFirst("\\?&", "?")
.replaceFirst("\\?$", "");
context.redirect(redirectURL);
LOG.debug("Redirecting request to remove code and state parameters");
return (false);
}
return (true);
}
catch(QAuthenticationException e)