[OID4VCI] Migrate CredentialsOffer to multiple grant types (#46947)

closes #46976

Signed-off-by: Thomas Diesler <tdiesler@proton.me>
This commit is contained in:
Thomas Diesler 2026-03-09 11:52:34 +01:00 committed by GitHub
parent b40a25908d
commit 46bcdb36a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 301 additions and 108 deletions

View file

@ -40,10 +40,10 @@ import org.keycloak.protocol.oid4vc.model.Claim;
import org.keycloak.protocol.oid4vc.model.ClaimsDescription;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oid4vc.utils.ClaimsPathPointer;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantType;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessor;
import org.keycloak.protocol.oidc.rar.InvalidAuthorizationDetailsException;
import org.keycloak.representations.AuthorizationDetailsJSONRepresentation;
@ -113,7 +113,7 @@ public class OID4VCAuthorizationDetailsProcessor implements AuthorizationDetails
// Skip if we're in pre-authorized code flow (it already has an offer state that will be updated)
// Pre-authorized flow sets VC_ISSUANCE_FLOW note on the client session
String vcIssuanceFlow = clientSession.getNote(PreAuthorizedCodeGrantType.VC_ISSUANCE_FLOW);
if (vcIssuanceFlow != null && vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) {
if (vcIssuanceFlow != null && vcIssuanceFlow.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) {
logger.debugf("Skipping offer state creation for pre-authorized code flow (offer state already exists and will be updated)");
return;
}

View file

@ -103,14 +103,12 @@ import org.keycloak.protocol.oid4vc.model.JwtProof;
import org.keycloak.protocol.oid4vc.model.NonceResponse;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.OfferResponseType;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oid4vc.model.Proofs;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
import org.keycloak.protocol.oid4vc.utils.ClaimsPathPointer;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantType;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessor;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AuthorizationDetailsJSONRepresentation;
@ -534,8 +532,7 @@ public class OID4VCIssuerEndpoint {
if (preAuthorized) {
String code = "urn:oid4vci:code:" + SecretGenerator.getInstance().randomString(64);
credOffer.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(
new PreAuthorizedCode().setPreAuthorizedCode(code)));
credOffer.addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode(code));
}
// Create the CredentialOfferState
@ -695,7 +692,7 @@ public class OID4VCIssuerEndpoint {
AuthenticatedClientSessionModel clientSession = getAuthenticatedClientSession();
String vcIssuanceFlow = clientSession.getNote(PreAuthorizedCodeGrantType.VC_ISSUANCE_FLOW);
if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) {
if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) {
// Use getAuthResult() instead of bearerTokenAuthenticator.authenticate() directly
// This ensures we benefit from the cachedAuthResult caching that prevents DPoP proof reuse
AccessToken accessToken = getAuthResult().token();

View file

@ -24,8 +24,6 @@ import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Time;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.saml.RandomSecret;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -55,9 +53,7 @@ public class CredentialOfferState {
@Transient
public Optional<String> getPreAuthorizedCode() {
return Optional.ofNullable(credentialsOffer.getGrants())
.map(PreAuthorizedGrant::getPreAuthorizedCode)
.map(PreAuthorizedCode::getPreAuthorizedCode);
return Optional.ofNullable(credentialsOffer.getPreAuthorizedCode());
}
public void generateTxCode() {

View file

@ -1,5 +1,5 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* Copyright 2026 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,42 +19,50 @@ package org.keycloak.protocol.oid4vc.model;
import java.util.Objects;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Container for the pre-authorized code to be used in a Credential Offer
* Container for the authorization code grant to be used in a Credential Offer
* <p>
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
*
* @author <a href="https://github.com/wistefan">Stefan Wiedemann</a>
* @author <a href="mailto:tdiesler@ibm.com">Thomas Diesler</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PreAuthorizedGrant {
public class AuthorizationCodeGrant implements CredentialOfferGrant {
@JsonProperty(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)
private PreAuthorizedCode preAuthorizedCode;
public static final String AUTH_CODE_GRANT_TYPE = "authorization_code";
public static final String ISSUER_STATE = "issuer_state";
public PreAuthorizedCode getPreAuthorizedCode() {
return preAuthorizedCode;
@Override
@JsonIgnore
public String getGrantType() {
return AUTH_CODE_GRANT_TYPE;
}
public PreAuthorizedGrant setPreAuthorizedCode(PreAuthorizedCode preAuthorizedCode) {
this.preAuthorizedCode = preAuthorizedCode;
@JsonProperty(ISSUER_STATE)
private String issuerState;
public String getIssuerState() {
return issuerState;
}
public AuthorizationCodeGrant setIssuerState(String issuerState) {
this.issuerState = issuerState;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PreAuthorizedGrant grant)) return false;
return Objects.equals(getPreAuthorizedCode(), grant.getPreAuthorizedCode());
if (!(o instanceof AuthorizationCodeGrant grant)) return false;
return Objects.equals(issuerState, grant.issuerState);
}
@Override
public int hashCode() {
return Objects.hash(getPreAuthorizedCode());
return Objects.hash(issuerState);
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2026 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oid4vc.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Container for the grants to be used in a Credential Offer
* <p>
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
*
* @author <a href="mailto:tdiesler@ibm.com">Thomas Diesler</a>
*/
public interface CredentialOfferGrant {
@JsonIgnore
String getGrantType();
}

View file

@ -0,0 +1,43 @@
package org.keycloak.protocol.oid4vc.model;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import static org.keycloak.protocol.oid4vc.model.AuthorizationCodeGrant.AUTH_CODE_GRANT_TYPE;
import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE;
public final class CredentialOfferGrantsDeserializer extends JsonDeserializer<Map<String, CredentialOfferGrant>> {
@Override
public Map<String, CredentialOfferGrant> deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
Map<String, CredentialOfferGrant> grants = new LinkedHashMap<>();
var mapper = (ObjectMapper) p.getCodec();
var node = mapper.readTree(p);
var fields = node.fieldNames();
while (fields.hasNext()) {
var grantType = fields.next();
var valueNode = node.get(grantType);
Class<? extends CredentialOfferGrant> target =
switch (grantType) {
case AUTH_CODE_GRANT_TYPE -> AuthorizationCodeGrant.class;
case PRE_AUTH_GRANT_TYPE -> PreAuthorizedCodeGrant.class;
default -> throw new InvalidFormatException(
p, "Unknown grant type key: " + grantType, grantType, CredentialOfferGrant.class);
};
CredentialOfferGrant grant = mapper.treeToValue(valueNode, target);
grants.put(grantType, grant);
}
return grants;
}
}

View file

@ -17,17 +17,24 @@
package org.keycloak.protocol.oid4vc.model;
import java.beans.Transient;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.util.JsonSerialization;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import static org.keycloak.OID4VCConstants.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER;
import static org.keycloak.protocol.oid4vc.model.AuthorizationCodeGrant.AUTH_CODE_GRANT_TYPE;
import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE;
/**
* Represents a CredentialsOffer according to the OID4VCI Spec
@ -45,8 +52,9 @@ public class CredentialsOffer {
@JsonProperty("credential_configuration_ids")
private List<String> credentialConfigurationIds;
// current implementation only supports pre-authorized codes.
private PreAuthorizedGrant grants;
@JsonProperty("grants")
@JsonDeserialize(using = CredentialOfferGrantsDeserializer.class)
private Map<String, CredentialOfferGrant> grants = new HashMap<>();
public String getCredentialIssuer() {
return credentialIssuer;
@ -57,7 +65,7 @@ public class CredentialsOffer {
return this;
}
@Transient
@JsonIgnore
public String getIssuerMetadataUrl() {
var metadataUrl = KeycloakUriBuilder
.fromUri(credentialIssuer)
@ -83,24 +91,55 @@ public class CredentialsOffer {
return this;
}
public PreAuthorizedGrant getGrants() {
return grants;
public CredentialOfferGrant getGrant(String grantType) {
return grants.get(grantType);
}
public CredentialsOffer setGrants(PreAuthorizedGrant grants) {
this.grants = grants;
public CredentialsOffer addGrant(CredentialOfferGrant grant) {
grants.put(grant.getGrantType(), grant);
return this;
}
@JsonIgnore
public AuthorizationCodeGrant getAuthorizationCodeGrant() {
return (AuthorizationCodeGrant) grants.get(AUTH_CODE_GRANT_TYPE);
}
@JsonIgnore
public String getIssuerState() {
return Optional.ofNullable(getAuthorizationCodeGrant())
.map(AuthorizationCodeGrant::getIssuerState)
.orElse(null);
}
@JsonIgnore
public PreAuthorizedCodeGrant getPreAuthorizedGrant() {
return (PreAuthorizedCodeGrant) grants.get(PRE_AUTH_GRANT_TYPE);
}
@JsonIgnore
public String getPreAuthorizedCode() {
return Optional.ofNullable(getPreAuthorizedGrant())
.map(PreAuthorizedCodeGrant::getPreAuthorizedCode)
.orElse(null);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CredentialsOffer that)) return false;
return Objects.equals(getCredentialIssuer(), that.getCredentialIssuer()) && Objects.equals(getCredentialConfigurationIds(), that.getCredentialConfigurationIds()) && Objects.equals(getGrants(), that.getGrants());
boolean match = Objects.equals(credentialIssuer, that.credentialIssuer);
match &= Objects.equals(credentialConfigurationIds, that.credentialConfigurationIds);
match &= Objects.equals(grants, that.grants);
return match;
}
@Override
public int hashCode() {
return Objects.hash(getCredentialIssuer(), getCredentialConfigurationIds(), getGrants());
return Objects.hash(credentialIssuer, credentialConfigurationIds, grants);
}
public String toString() {
return JsonSerialization.valueAsString(this);
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2026 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oid4vc.model;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import org.keycloak.util.JsonSerialization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Container for the issuer state used by the authorization code grant in a Credential Offer
* <p>
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
*
* @author <a href="mailto:tdiesler@ibm.com">Thomas Diesler</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IssuerState {
@JsonProperty("credential_offer_id")
private String credentialOfferId;
public String getCredentialOfferId() {
return credentialOfferId;
}
public IssuerState setCredentialOfferId(String credentialOfferId) {
this.credentialOfferId = credentialOfferId;
return this;
}
public static IssuerState fromEncodedString(String encoded) {
byte[] encodedBytes = encoded.getBytes(StandardCharsets.UTF_8);
String value = new String(Base64.getUrlDecoder().decode(encodedBytes));
return JsonSerialization.valueFromString(value, IssuerState.class);
}
public String encodeToString() {
String value = JsonSerialization.valueAsString(this);
return Base64.getUrlEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IssuerState grant)) return false;
return Objects.equals(credentialOfferId, grant.credentialOfferId);
}
@Override
public int hashCode() {
return Objects.hash(credentialOfferId);
}
}

View file

@ -19,32 +19,45 @@ package org.keycloak.protocol.oid4vc.model;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents a pre-authorized grant, as used by the Credential Offer in OID4VCI
* Container for the pre-authorized code to be used in a Credential Offer
* <p>
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
*
* @author <a href="https://github.com/wistefan">Stefan Wiedemann</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PreAuthorizedCode {
public class PreAuthorizedCodeGrant implements CredentialOfferGrant {
@JsonProperty("pre-authorized_code")
public static final String PRE_AUTH_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code";
public static final String AUTHORIZATION_SERVER_PARAM = "authorization_server";
public static final String CODE_REQUEST_PARAM = "pre-authorized_code";
public static final String TX_CODE_PARAM = "tx_code";
@Override
@JsonIgnore
public String getGrantType() {
return PRE_AUTH_GRANT_TYPE;
}
@JsonProperty(CODE_REQUEST_PARAM)
private String preAuthorizedCode;
@JsonProperty("tx_code")
@JsonProperty(TX_CODE_PARAM)
private TxCode txCode;
@JsonProperty("authorization_server")
@JsonProperty(AUTHORIZATION_SERVER_PARAM)
private String authorizationServer;
public String getPreAuthorizedCode() {
return preAuthorizedCode;
}
public PreAuthorizedCode setPreAuthorizedCode(String preAuthorizedCode) {
public PreAuthorizedCodeGrant setPreAuthorizedCode(String preAuthorizedCode) {
this.preAuthorizedCode = preAuthorizedCode;
return this;
}
@ -53,7 +66,7 @@ public class PreAuthorizedCode {
return txCode;
}
public PreAuthorizedCode setTxCode(TxCode txCode) {
public PreAuthorizedCodeGrant setTxCode(TxCode txCode) {
this.txCode = txCode;
return this;
}
@ -62,7 +75,7 @@ public class PreAuthorizedCode {
return authorizationServer;
}
public PreAuthorizedCode setAuthorizationServer(String authorizationServer) {
public PreAuthorizedCodeGrant setAuthorizationServer(String authorizationServer) {
this.authorizationServer = authorizationServer;
return this;
}
@ -70,12 +83,13 @@ public class PreAuthorizedCode {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PreAuthorizedCode that)) return false;
return Objects.equals(getPreAuthorizedCode(), that.getPreAuthorizedCode()) && Objects.equals(getTxCode(), that.getTxCode()) && Objects.equals(getAuthorizationServer(), that.getAuthorizationServer());
if (!(o instanceof PreAuthorizedCodeGrant that)) return false;
return Objects.equals(preAuthorizedCode, that.preAuthorizedCode)
&& Objects.equals(txCode, that.txCode) && Objects.equals(authorizationServer, that.authorizationServer);
}
@Override
public int hashCode() {
return Objects.hash(getPreAuthorizedCode(), getTxCode(), getAuthorizationServer());
return Objects.hash(preAuthorizedCode, txCode, authorizationServer);
}
}

View file

@ -43,11 +43,11 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.grants.OAuth2GrantType;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
import org.keycloak.protocol.saml.SamlClient;
@ -132,7 +132,7 @@ public class TokenEndpoint {
if (!grantType.equals(OAuth2Constants.UMA_GRANT_TYPE)
// pre-authorized grants are not necessarily used by known clients.
&& !grantType.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) {
&& !grantType.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) {
checkClient();
checkParameterDuplicated();
}

View file

@ -42,6 +42,7 @@ import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider;
import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStorage;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oid4vc.utils.OID4VCUtil;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
@ -80,12 +81,12 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase {
}
// See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-request
String preAuthCode = formParams.getFirst(PreAuthorizedCodeGrantTypeFactory.CODE_REQUEST_PARAM);
String txCode = formParams.getFirst(PreAuthorizedCodeGrantTypeFactory.TX_CODE_PARAM);
String preAuthCode = formParams.getFirst(PreAuthorizedCodeGrant.CODE_REQUEST_PARAM);
String txCode = formParams.getFirst(PreAuthorizedCodeGrant.TX_CODE_PARAM);
if (preAuthCode == null) {
// See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-request
String errorMessage = "Missing parameter: " + PreAuthorizedCodeGrantTypeFactory.CODE_REQUEST_PARAM;
String errorMessage = "Missing parameter: " + PreAuthorizedCodeGrant.CODE_REQUEST_PARAM;
event.detail(REASON, errorMessage).error(Errors.INVALID_CODE);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST,
errorMessage, Response.Status.BAD_REQUEST);
@ -153,7 +154,7 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, clientModel, userSession);
clientSession.setNote(OIDCLoginProtocol.ISSUER, credOffer.getCredentialIssuer());
clientSession.setNote(VC_ISSUANCE_FLOW, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE);
clientSession.setNote(VC_ISSUANCE_FLOW, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE);
List<ClientScopeModel> credentialScopes = resolveCredentialScopesForCredentialOffer(credOffer.getCredentialConfigurationIds());
Set<ClientScopeModel> requestedScopes = TokenManager.getRequestedClientScopes(session, OAuth2Constants.SCOPE_OPENID, clientModel, targetUserModel)
@ -161,7 +162,7 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase {
requestedScopes.addAll(credentialScopes);
ClientSessionContext sessionContext = fromClientSessionAndClientScopes(clientSession, requestedScopes, null, session);
sessionContext.setAttribute(Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE);
sessionContext.setAttribute(Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE);
// set the client as retrieved from the pre-authorized session
session.getContext().setClient(clientModel);

View file

@ -23,6 +23,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE;
/**
* Factory for Pre-Authorized Code Grant
*
@ -30,10 +32,6 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
*/
public class PreAuthorizedCodeGrantTypeFactory implements OAuth2GrantTypeFactory, EnvironmentDependentProviderFactory {
public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code";
public static final String CODE_REQUEST_PARAM = "pre-authorized_code";
public static final String TX_CODE_PARAM = "tx_code";
@Override
public OAuth2GrantType create(KeycloakSession session) {
return new PreAuthorizedCodeGrantType();
@ -53,7 +51,7 @@ public class PreAuthorizedCodeGrantTypeFactory implements OAuth2GrantTypeFactory
@Override
public String getId() {
return GRANT_TYPE;
return PRE_AUTH_GRANT_TYPE;
}
@Override

View file

@ -105,7 +105,7 @@ public class OID4VCBasicWallet {
CredentialsOffer credOffer = getCredentialOffer(ctx, credOfferUri)
.send().getCredentialsOffer();
String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode();
String preAuthCode = credOffer.getPreAuthorizedCode();
assertNotNull(preAuthCode, "No PreAuth Code");
return credOffer;

View file

@ -166,7 +166,7 @@ public class OID4VCCredentialOfferMatrixTest extends OID4VCIssuerTestBase {
// Create Pre-Authorized CredentialOffer
//
CredentialsOffer credOffer = wallet.createPreAuthCredentialOffer(ctx, null);
String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode();
String preAuthCode = credOffer.getPreAuthorizedCode();
// Redeem Pre-Authorized Code for AccessToken
//
@ -195,7 +195,7 @@ public class OID4VCCredentialOfferMatrixTest extends OID4VCIssuerTestBase {
// Create Pre-Authorized CredentialOffer
//
CredentialsOffer credOffer = wallet.createPreAuthCredentialOffer(ctx, ctx.holder);
String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode();
String preAuthCode = credOffer.getPreAuthorizedCode();
// Redeem Pre-Authorized Code for AccessToken
//

View file

@ -5,7 +5,7 @@ import java.util.List;
import org.keycloak.OAuth2Constants;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.testsuite.util.oauth.AbstractHttpPostRequest;
import org.keycloak.testsuite.util.oauth.AbstractOAuthClient;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
@ -34,8 +34,8 @@ public class PreAuthorizedCodeGrantRequest extends AbstractHttpPostRequest<PreAu
@Override
protected void initRequest() {
parameter(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE);
parameter(PreAuthorizedCodeGrantTypeFactory.CODE_REQUEST_PARAM, preAuthCode);
parameter(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE);
parameter(PreAuthorizedCodeGrant.CODE_REQUEST_PARAM, preAuthCode);
}
@Override

View file

@ -90,8 +90,7 @@ import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider;
import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferState;
import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStorage;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oidc.encode.AccessTokenContext;
import org.keycloak.protocol.oidc.encode.TokenContextEncoderProvider;
import org.keycloak.provider.Provider;
@ -1132,8 +1131,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
CredentialsOffer credOffer = new CredentialsOffer()
.setCredentialIssuer(OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()))
.setCredentialConfigurationIds(List.of("oid4vc_natural_person_sd"))
.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(
new PreAuthorizedCode().setPreAuthorizedCode(code)));
.addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode(code));
String userId = userSession.getUser().getId();
var offerStorage = session.getProvider(CredentialOfferStorage.class);

View file

@ -26,8 +26,8 @@ import jakarta.ws.rs.core.UriBuilder;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
@ -89,7 +89,7 @@ public class PreAuthorizedGrantTest extends AbstractTestRealmKeycloakTest {
getUserSession();
HttpPost post = new HttpPost(getTokenEndpoint());
List<NameValuePair> parameters = new LinkedList<>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE));
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8);
post.setEntity(formEntity);
@ -127,7 +127,7 @@ public class PreAuthorizedGrantTest extends AbstractTestRealmKeycloakTest {
private AccessTokenResponse postCode(String preAuthorizedCode) throws Exception {
HttpPost post = new HttpPost(getTokenEndpoint());
List<NameValuePair> parameters = new LinkedList<>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE));
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE));
parameters.add(new BasicNameValuePair("pre-authorized_code", preAuthorizedCode));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8);
post.setEntity(formEntity);

View file

@ -36,7 +36,6 @@ import org.keycloak.protocol.oid4vc.model.CredentialResponse;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.ErrorType;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
@ -304,7 +303,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -365,7 +364,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -415,7 +414,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -446,7 +445,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -477,7 +476,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -510,7 +509,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -541,7 +540,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();
@ -560,7 +559,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
// Send empty authorization_details array - should fail
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(List.of())
.send();
@ -579,7 +578,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
// The system should generate authorization_details based on credential_configuration_ids from the credential offer
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.send();
@ -617,11 +616,10 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
String token = getBearerToken(oauth, client, getCredentialClientScope().getName());
Oid4vcTestContext ctx = prepareOid4vcTestContext(token);
PreAuthorizedCode preAuthorizedCode = ctx.credentialsOffer.getGrants().getPreAuthorizedCode();
// Step 1: Request token without authorization_details parameter (no scope needed)
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(preAuthorizedCode.getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.send();
@ -700,11 +698,10 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
public void testPreAuthorizedCodeTokenEndpointRestriction() throws Exception {
String token = getBearerToken(oauth, client, getCredentialClientScope().getName());
Oid4vcTestContext ctx = prepareOid4vcTestContext(token);
PreAuthorizedCode preAuthorizedCode = ctx.credentialsOffer.getGrants().getPreAuthorizedCode();
// Step 1: Get pre-authorized code token
AccessTokenResponse accessTokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(preAuthorizedCode.getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.send();
@ -801,7 +798,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
// Step 1: Request token without authorization_details parameter
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.send();
@ -875,7 +872,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
List<OID4VCAuthorizationDetail> authDetails = List.of(authDetail);
AccessTokenResponse tokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode())
.endpoint(ctx.openidConfig.getTokenEndpoint())
.authorizationDetails(authDetails)
.send();

View file

@ -35,8 +35,7 @@ import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStor
import org.keycloak.protocol.oid4vc.model.CredentialOfferURI;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.ErrorType;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.cors.Cors;
import org.keycloak.testsuite.AssertEvents;
@ -327,7 +326,7 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest {
String nonce = testingClient.server(TEST_REALM_NAME).fetchString(session -> {
CredentialsOffer credOffer = new CredentialsOffer()
.setCredentialIssuer(issuerPath)
.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("test-code")))
.addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode("test-code"))
.setCredentialConfigurationIds(List.of(jwtTypeCredentialConfigurationIdName));
CredentialOfferStorage offerStorage = session.getProvider(CredentialOfferStorage.class);

View file

@ -20,8 +20,8 @@
package org.keycloak.testsuite.oid4vc.issuance.signing;
import org.keycloak.common.Profile;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oidc.grants.OAuth2GrantTypeSpi;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.feature.AbstractFeatureStateTest;
@ -35,7 +35,7 @@ public class OID4VCGrantFeatureTest extends AbstractFeatureStateTest {
@Override
public String getFeatureProviderId() {
return PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE;
return PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE;
}
@Override

View file

@ -53,12 +53,10 @@ import org.keycloak.protocol.oid4vc.model.ErrorResponse;
import org.keycloak.protocol.oid4vc.model.ErrorType;
import org.keycloak.protocol.oid4vc.model.JwtProof;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oid4vc.model.Proofs;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
@ -238,7 +236,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
authenticator.setTokenString(token);
CredentialsOffer credOffer = new CredentialsOffer()
.setCredentialIssuer("the-issuer")
.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("the-code")))
.addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode("the-code"))
.setCredentialConfigurationIds(List.of("credential-configuration-id"));
CredentialOfferStorage offerStorage = session.getProvider(CredentialOfferStorage.class);
@ -484,11 +482,11 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
.getOidcConfiguration();
assertNotNull("A token endpoint should be included.", openidConfig.getTokenEndpoint());
assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE));
assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE));
// 5. Get an access token for the pre-authorized code
AccessTokenResponse accessTokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(credentialsOffer.getPreAuthorizedCode())
.endpoint(openidConfig.getTokenEndpoint())
.send();
@ -1222,8 +1220,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
.send()
.getCredentialsOffer();
assertNotNull("Credential offer should not be null", credentialsOffer);
assertNotNull("Pre-authorized grant should be present", credentialsOffer.getGrants());
String preAuthorizedCode = credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode();
String preAuthorizedCode = credentialsOffer.getPreAuthorizedCode();
assertNotNull("Pre-authorized code value should not be null", preAuthorizedCode);
// 3. Second access to the same credential offer URL - should fail with replay protection error
@ -1347,8 +1344,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
.send()
.getCredentialsOffer();
assertNotNull("Credential offer should not be null", credentialsOffer);
assertNotNull("Pre-authorized grant should be present", credentialsOffer.getGrants());
String preAuthorizedCode = credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode();
String preAuthorizedCode = credentialsOffer.getPreAuthorizedCode();
assertNotNull("Pre-authorized code value should not be null", preAuthorizedCode);
// 3. Immediately perform the Token Request (Pre-Authorized Code Grant) using the valid code

View file

@ -49,9 +49,9 @@ import org.keycloak.protocol.oid4vc.model.CredentialRequest;
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant;
import org.keycloak.protocol.oid4vc.model.Proofs;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientScopeRepresentation;
@ -408,11 +408,11 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
.getOidcConfiguration();
assertNotNull("A token endpoint should be included.", openidConfig.getTokenEndpoint());
assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE));
assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE));
// 5. Get an access token for the pre-authorized code
AccessTokenResponse accessTokenResponse = oauth.oid4vc()
.preAuthorizedCodeGrantRequest(credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode())
.preAuthorizedCodeGrantRequest(credentialsOffer.getPreAuthorizedCode())
.endpoint(openidConfig.getTokenEndpoint())
.send();