[KEYCLOAK-8575] oidc idp basic auth (#6268)

* [KEYCLOAK-8575] Allow to choose between basic auth and form auth for oidc idp

* uncomment ui and add tests

* move basic auth to abstract identity provider (except for getting refresh tokens)

* removed duplications
This commit is contained in:
rradillen 2019-09-19 14:36:16 +02:00 committed by Marek Posolda
parent b833ce9dd3
commit b71198af9f
7 changed files with 212 additions and 126 deletions

View file

@ -27,6 +27,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
@ -35,6 +36,7 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.common.util.Base64;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.util.JsonSerialization;
@ -46,15 +48,12 @@ import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import org.apache.http.client.methods.HttpDelete;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -133,6 +132,12 @@ public class SimpleHttp {
return this;
}
public SimpleHttp authBasic(final String username, final String password) {
final String basicCredentials = String.format("%s:%s", username, password);
header("Authorization", "Basic " + Base64.encodeBytes(basicCredentials.getBytes()));
return this;
}
public SimpleHttp acceptJson() {
if (headers == null || !headers.containsKey("Accept")) {
header("Accept", "application/json");

View file

@ -25,8 +25,8 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ExchangeExternalToken;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.ClientConnection;
@ -47,10 +47,10 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ErrorResponseException;
@ -465,6 +465,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
.param(OAuth2Constants.CLIENT_ASSERTION, jws);
} else {
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
if (getConfig().isBasicAuthentication()) {
return tokenRequest.authBasic(getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret()));
}
return tokenRequest
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, vaultStringSecret.get().orElse(getConfig().getClientSecret()));

View file

@ -100,6 +100,10 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
return false;
}
public boolean isBasicAuthentication(){
return getClientAuthMethod().equals(OIDCLoginProtocol.CLIENT_SECRET_BASIC);
}
public boolean isUiLocales() {
return Boolean.valueOf(getConfig().get("uiLocales"));
}
@ -119,4 +123,4 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public void setForwardParameters(String forwardParameters) {
getConfig().put("forwardParameters", forwardParameters);
}
}
}

View file

@ -40,6 +40,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;
@ -94,41 +95,20 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
return new OIDCEndpoint(callback, realm, event);
}
protected class OIDCEndpoint extends Endpoint {
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
/**
* Returns access token response as a string from a refresh token invocation on the remote OIDC broker
*
* @param session
* @param userSession
* @return
*/
public String refreshTokenForLogout(KeycloakSession session, UserSessionModel userSession) {
String refreshToken = userSession.getNote(FEDERATED_REFRESH_TOKEN);
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
return getRefreshTokenRequest(session, refreshToken, getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
} catch (IOException e) {
throw new RuntimeException(e);
}
@GET
@Path("logout_response")
public Response logoutResponse(@QueryParam("state") String state) {
if (state == null){
logger.error("no state parameter returned");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
if (userSession == null) {
logger.error("no valid user session");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.error("usersession in different state");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.SESSION_NOT_ACTIVE);
}
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
}
}
@Override
@ -180,21 +160,66 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
}
/**
* Returns access token response as a string from a refresh token invocation on the remote OIDC broker
*
* @param session
* @param userSession
* @return
*/
public String refreshTokenForLogout(KeycloakSession session, UserSessionModel userSession) {
String refreshToken = userSession.getNote(FEDERATED_REFRESH_TOKEN);
@Override
protected Response exchangeStoredToken(UriInfo uriInfo, EventBuilder event, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject) {
FederatedIdentityModel model = session.users().getFederatedIdentity(tokenSubject, getConfig().getAlias(), authorizedClient.getRealm());
if (model == null || model.getToken() == null) {
event.detail(Details.REASON, "requested_issuer is not linked");
event.error(Errors.INVALID_TOKEN);
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
return SimpleHttp.doPost(getConfig().getTokenUrl(), session)
.param("refresh_token", refreshToken)
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_REFRESH_TOKEN)
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
String modelTokenString = model.getToken();
AccessTokenResponse tokenResponse = JsonSerialization.readValue(modelTokenString, AccessTokenResponse.class);
Integer exp = (Integer) tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
if (exp != null && exp < Time.currentTime()) {
if (tokenResponse.getRefreshToken() == null) {
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
String response = getRefreshTokenRequest(session, tokenResponse.getRefreshToken(),
getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
if (response.contains("error")) {
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
model.setToken(null);
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
event.detail(Details.REASON, "requested_issuer token expired");
event.error(Errors.INVALID_TOKEN);
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
if (newResponse.getExpiresIn() > 0) {
int accessTokenExpiration = Time.currentTime() + (int) newResponse.getExpiresIn();
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
}
if (newResponse.getRefreshToken() == null && tokenResponse.getRefreshToken() != null) {
newResponse.setRefreshToken(tokenResponse.getRefreshToken());
newResponse.setRefreshExpiresIn(tokenResponse.getRefreshExpiresIn());
}
response = JsonSerialization.writeValueAsString(newResponse);
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
if (oldToken != null && oldToken.equals(tokenResponse.getToken())) {
int accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + (int) newResponse.getExpiresIn() : 0;
tokenUserSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(accessTokenExpiration));
tokenUserSession.setNote(FEDERATED_REFRESH_TOKEN, newResponse.getRefreshToken());
tokenUserSession.setNote(FEDERATED_ACCESS_TOKEN, newResponse.getToken());
tokenUserSession.setNote(FEDERATED_ID_TOKEN, newResponse.getIdToken());
}
model.setToken(response);
tokenResponse = newResponse;
} else if (exp != null) {
tokenResponse.setExpiresIn(exp - Time.currentTime());
}
tokenResponse.setIdToken(null);
tokenResponse.setRefreshToken(null);
tokenResponse.setRefreshExpiresIn(0);
tokenResponse.getOtherClaims().clear();
tokenResponse.getOtherClaims().put(OAuth2Constants.ISSUED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE);
tokenResponse.getOtherClaims().put(ACCOUNT_LINK_URL, getLinkingUrl(uriInfo, authorizedClient, tokenUserSession));
event.success();
return Response.ok(tokenResponse).type(MediaType.APPLICATION_JSON_TYPE).build();
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -224,72 +249,18 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
@Override
protected Response exchangeStoredToken(UriInfo uriInfo, EventBuilder event, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject) {
FederatedIdentityModel model = session.users().getFederatedIdentity(tokenSubject, getConfig().getAlias(), authorizedClient.getRealm());
if (model == null || model.getToken() == null) {
event.detail(Details.REASON, "requested_issuer is not linked");
event.error(Errors.INVALID_TOKEN);
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
String modelTokenString = model.getToken();
AccessTokenResponse tokenResponse = JsonSerialization.readValue(modelTokenString, AccessTokenResponse.class);
Integer exp = (Integer) tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
if (exp != null && exp < Time.currentTime()) {
if (tokenResponse.getRefreshToken() == null) {
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
String response = SimpleHttp.doPost(getConfig().getTokenUrl(), session)
.param("refresh_token", tokenResponse.getRefreshToken())
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_REFRESH_TOKEN)
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
if (response.contains("error")) {
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
model.setToken(null);
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
event.detail(Details.REASON, "requested_issuer token expired");
event.error(Errors.INVALID_TOKEN);
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject);
}
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
if (newResponse.getExpiresIn() > 0) {
int accessTokenExpiration = Time.currentTime() + (int) newResponse.getExpiresIn();
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
}
if (newResponse.getRefreshToken() == null && tokenResponse.getRefreshToken() != null) {
newResponse.setRefreshToken(tokenResponse.getRefreshToken());
newResponse.setRefreshExpiresIn(tokenResponse.getRefreshExpiresIn());
}
response = JsonSerialization.writeValueAsString(newResponse);
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
if (oldToken != null && oldToken.equals(tokenResponse.getToken())) {
int accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + (int) newResponse.getExpiresIn() : 0;
tokenUserSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(accessTokenExpiration));
tokenUserSession.setNote(FEDERATED_REFRESH_TOKEN, newResponse.getRefreshToken());
tokenUserSession.setNote(FEDERATED_ACCESS_TOKEN, newResponse.getToken());
tokenUserSession.setNote(FEDERATED_ID_TOKEN, newResponse.getIdToken());
}
model.setToken(response);
tokenResponse = newResponse;
} else if (exp != null) {
tokenResponse.setExpiresIn(exp - Time.currentTime());
}
tokenResponse.setIdToken(null);
tokenResponse.setRefreshToken(null);
tokenResponse.setRefreshExpiresIn(0);
tokenResponse.getOtherClaims().clear();
tokenResponse.getOtherClaims().put(OAuth2Constants.ISSUED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE);
tokenResponse.getOtherClaims().put(ACCOUNT_LINK_URL, getLinkingUrl(uriInfo, authorizedClient, tokenUserSession));
event.success();
return Response.ok(tokenResponse).type(MediaType.APPLICATION_JSON_TYPE).build();
} catch (IOException e) {
throw new RuntimeException(e);
protected SimpleHttp getRefreshTokenRequest(KeycloakSession session, String refreshToken, String clientId, String clientSecret) {
if (OIDCLoginProtocol.CLIENT_SECRET_BASIC.equals(getConfig().getClientAuthMethod())) {
return SimpleHttp.doPost(getConfig().getTokenUrl(), session)
.param("refresh_token", refreshToken)
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_REFRESH_TOKEN)
.authBasic(clientId, clientSecret);
}
return SimpleHttp.doPost(getConfig().getTokenUrl(), session)
.param("refresh_token", refreshToken)
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_REFRESH_TOKEN)
.param(OAUTH2_PARAMETER_CLIENT_ID, clientId)
.param(OAUTH2_PARAMETER_CLIENT_SECRET, clientSecret);
}
@Override
@ -317,11 +288,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
event.success();
return Response.ok(tokenResponse).type(MediaType.APPLICATION_JSON_TYPE).build();
}
String response = SimpleHttp.doPost(getConfig().getTokenUrl(), session)
.param("refresh_token", refreshToken)
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_REFRESH_TOKEN)
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
String response = getRefreshTokenRequest(session, refreshToken, getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret())).asString();
if (response.contains("error")) {
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
event.detail(Details.REASON, "requested_issuer token expired");
@ -347,6 +314,42 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
}
protected class OIDCEndpoint extends Endpoint {
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
}
@GET
@Path("logout_response")
public Response logoutResponse(@QueryParam("state") String state) {
if (state == null){
logger.error("no state parameter returned");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
if (userSession == null) {
logger.error("no valid user session");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.error("usersession in different state");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.SESSION_NOT_ACTIVE);
}
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
}
}
@Override
public BrokeredIdentityContext getFederatedIdentity(String response) {

View file

@ -141,6 +141,42 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertEquals(ComponentRepresentation.SECRET_VALUE, rep.getConfig().get("clientSecret"));
}
@Test
public void testCreateWithBasicAuth() {
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
newIdentityProvider.getConfig().put("clientAuthMethod",OIDCLoginProtocol.CLIENT_SECRET_BASIC);
create(newIdentityProvider);
IdentityProviderResource identityProviderResource = realm.identityProviders().get("new-identity-provider");
assertNotNull(identityProviderResource);
IdentityProviderRepresentation representation = identityProviderResource.toRepresentation();
assertNotNull(representation);
assertNotNull(representation.getInternalId());
assertEquals("new-identity-provider", representation.getAlias());
assertEquals("oidc", representation.getProviderId());
assertEquals("clientId", representation.getConfig().get("clientId"));
assertEquals(ComponentRepresentation.SECRET_VALUE, representation.getConfig().get("clientSecret"));
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, representation.getConfig().get("clientAuthMethod"));
assertTrue(representation.isEnabled());
assertFalse(representation.isStoreToken());
assertFalse(representation.isTrustEmail());
assertEquals("some secret value", testingClient.testing("admin-client-test").getIdentityProviderConfig("new-identity-provider").get("clientSecret"));
IdentityProviderRepresentation rep = realm.identityProviders().findAll().stream().filter(i -> i.getAlias().equals("new-identity-provider")).findFirst().get();
assertEquals(ComponentRepresentation.SECRET_VALUE, rep.getConfig().get("clientSecret"));
}
@Test
public void testCreateWithJWT() {
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");

View file

@ -0,0 +1,35 @@
package org.keycloak.testsuite.broker;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.arquillian.SuiteContext;
import java.util.List;
import java.util.Map;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider;
public class KcOidcBrokerClientSecretBasicAuthTest extends KcOidcBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfigurationWithBasicAuthAuthentication();
}
private class KcOidcBrokerConfigurationWithBasicAuthAuthentication extends KcOidcBrokerConfiguration {
@Override
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(suiteContext, config);
config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_BASIC);
return idp;
}
}
}

View file

@ -174,7 +174,7 @@
ng-model="identityProvider.config.clientAuthMethod"
required>
<option id="clientAuth_post" name="clientAuth" value="client_secret_post" selected>{{:: 'client-auth.client_secret_post' | translate}}</option>
<!-- <option id="clientAuth_basic" name="clientAuth" value="client_secret_basic">{{:: 'client-auth.client_secret_basic' | translate}}</option> -->
<option id="clientAuth_basic" name="clientAuth" value="client_secret_basic">{{:: 'client-auth.client_secret_basic' | translate}}</option>
<option id="clientAuth_secret_jwt" name="clientAuth" value="client_secret_jwt">{{:: 'client-auth.client_secret_jwt' | translate}}</option>
<option id="clientAuth_privatekey_jwt" name="clientAuth" value="private_key_jwt">{{:: 'client-auth.private_key_jwt' | translate}}</option>
</select>
@ -337,4 +337,4 @@
</div>
<kc-menu></kc-menu>
<kc-menu></kc-menu>