A ID Verificada do Microsoft Entra inclui a API REST de Serviço de Solicitação. Essa API permite que você emita e verifique as credenciais. Este artigo mostra como começar a usar a API REST do Serviço de Solicitação.
Token de acesso à API
Seu aplicativo precisa incluir um token de acesso válido com as permissões necessárias para que ele possa acessar a API REST do Serviço de Solicitação. Os tokens de acesso emitidos pela plataforma de identidade da Microsoft contêm informações (escopos) que a API REST do Serviço de Solicitação usa para validar o chamador. Um token de acesso garante que o chamador tenha as permissões adequadas para executar a operação que está solicitando.
Para obter um token de acesso, seu aplicativo deve ser registrado na plataforma de identidade da Microsoft e ser autorizado por um administrador para acesso à API REST do Serviço de Solicitação. Se você ainda não registrou o aplicativo verifiable-credentials-app, confiracomo registrar o aplicativo, em seguida gere o segredo do aplicativo.
Obter um token de acesso
Use o fluxo de concessão de credenciais do cliente OAuth 2.0 para adquirir o token de acesso usando a plataforma de identidade da Microsoft. Use uma biblioteca confiável para essa finalidade. Neste tutorial, usamos a Biblioteca de Autenticação da Microsoft (MSAL). A MSAL simplifica a adição de autenticação e autorização a um aplicativo que pode chamar uma API Web segura.
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 //Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=3db474b9-6a0c-4840-96ac-1fceb342124f/.default
&client_secret=sampleCredentia1s
&grant_type=client_credentials
// Initialize MSAL library by using the following code
ConfidentialClientApplicationBuilder.Create(AppSettings.ClientId)
.WithClientSecret(AppSettings.ClientSecret)
.WithAuthority(new Uri(AppSettings.Authority))
.Build();
// Acquire an access token
result = await app.AcquireTokenForClient(AppSettings.Scopes)
.ExecuteAsync();
// Initialize MSAL library by using the following code
const msalConfig = {
auth: {
clientId: config.azClientId,
authority: `https://login.microsoftonline.com/${config.azTenantId}`,
clientSecret: config.azClientSecret,
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const cca = new msal.ConfidentialClientApplication(msalConfig);
const msalClientCredentialRequest = {
scopes: ["3db474b9-6a0c-4840-96ac-1fceb342124f/.default"],
skipCache: false,
};
module.exports.msalCca = cca;
module.exports.msalClientCredentialRequest = msalClientCredentialRequest;
// Acquire an access token
const result = await mainApp.msalCca.acquireTokenByClientCredential(mainApp.msalClientCredentialRequest);
if ( result ) {
accessToken = result.accessToken;
}
# Initialize MSAL library by using the following code
msalCca = msal.ConfidentialClientApplication( config["azClientId"],
authority="https://login.microsoftonline.com/" + config["azTenantId"],
client_credential=config["azClientSecret"],
)
# Acquire an access token
accessToken = ""
result = msalCca.acquire_token_for_client( scopes="3db474b9-6a0c-4840-96ac-1fceb342124f/.default" )
if "access_token" in result:
accessToken = result['access_token']
// Initialize MSAL library by using the following code
ConfidentialClientApplication app = ConfidentialClientApplication.builder(
clientId,
ClientCredentialFactory.createFromSecret(clientSecret))
.authority(authority)
.build();
// Acquire an access token
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
Collections.singleton(scope))
.build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
IAuthenticationResult result = future.get();
return result.accessToken();
No código anterior, forneça os seguintes parâmetros:
Parâmetro |
Condição |
Descrição |
Autoridade |
Necessário |
O locatário do diretório no qual o aplicativo planeja operar. Por exemplo: https://login.microsoftonline.com/{your-tenant} . (Substitua your-tenant pela ID do inquilino ou pelo nome.) |
ID do cliente |
Necessário |
A ID do aplicativo atribuída ao seu aplicativo. Você pode encontrar essas informações no portal do Azure, onde registrou seu aplicativo. |
Segredo do cliente |
Necessário |
O segredo do cliente gerado para seu aplicativo. |
Escopos |
Necessário |
Deve ser definido como 3db474b9-6a0c-4840-96ac-1fceb342124f/.default . Essa configuração produz um token de acesso com uma declaração de funções de VerifiableCredential.Create.All . |
Para obter mais informações sobre como obter um token de acesso usando a identidade de um aplicativo de console, consulte um dos seguintes artigos:
Você também pode acessar uma solicitação de token com um certificado, em vez de um segredo do cliente.
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 //Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=3db474b9-6a0c-4840-96ac-1fceb342124f/.default
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials
// Initialize MSAL library by using the following code
X509Certificate2 certificate = AppSettings.ReadCertificate(AppSettings.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(AppSettings.ClientId)
.WithCertificate(certificate)
.WithAuthority(new Uri(AppSettings.Authority))
.Build();
// Acquire an access token
result = await app.AcquireTokenForClient(AppSettings.Scopes)
.ExecuteAsync();
// Initialize MSAL library by using the following code
const msalConfig = {
auth: {
clientId: config.azClientId,
authority: `https://login.microsoftonline.com/${config.azTenantId}`,
clientCertificate: {
thumbprint: "CERT_THUMBPRINT", // a 40-digit hexadecimal string
privateKey: "CERT_PRIVATE_KEY"
}
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const cca = new msal.ConfidentialClientApplication(msalConfig);
const msalClientCredentialRequest = {
scopes: ["3db474b9-6a0c-4840-96ac-1fceb342124f/.default"],
skipCache: false,
};
module.exports.msalCca = cca;
module.exports.msalClientCredentialRequest = msalClientCredentialRequest;
// Acquire an access token
const result = await mainApp.msalCca.acquireTokenByClientCredential(mainApp.msalClientCredentialRequest);
if ( result ) {
accessToken = result.accessToken;
}
# Initialize MSAL library by using the following code
with open(config["azCertificatePrivateKeyLocation"], "rb") as file:
private_key = file.read()
with open(config["azCertificateLocation"]) as file:
public_certificate = file.read()
cert = load_pem_x509_certificate(data=bytes(public_certificate, 'UTF-8'), backend=default_backend())
thumbprint = (cert.fingerprint(hashes.SHA1()).hex())
msalCca = msal.ConfidentialClientApplication( config["azClientId"],
authority="https://login.microsoftonline.com/" + config["azTenantId"],
client_credential={
"private_key": private_key,
"thumbprint": thumbprint,
"public_certificate": public_certificate
}
)
# Acquire an access token
accessToken = ""
result = msalCca.acquire_token_for_client( scopes="3db474b9-6a0c-4840-96ac-1fceb342124f/.default" )
if "access_token" in result:
accessToken = result['access_token']
// Initialize MSAL library by using the following code
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Files.readAllBytes(Paths.get(certKeyLocation)));
PrivateKey key = KeyFactory.getInstance("RSA").generatePrivate(spec);
java.io.InputStream certStream = (java.io.InputStream)new ByteArrayInputStream(Files.readAllBytes(Paths.get(certLocation)));
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certStream);
ConfidentialClientApplication app = ConfidentialClientApplication.builder(
clientId,
ClientCredentialFactory.createFromCertificate(key, cert))
.authority(authority)
.build();
// Acquire an access token
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
Collections.singleton(scope))
.build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
IAuthenticationResult result = future.get();
return result.accessToken();
Chamar a API
Para emitir ou verificar uma credencial verificável:
Construa uma solicitação HTTP POST para a API REST do Serviço de Solicitação. A ID do locatário não é mais necessária na URL porque está presente como uma declaração no token de acesso.
Problema
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createIssuanceRequest
Verificar
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createPresentationRequest
Anexe o token de acesso como um token de portador ao cabeçalho de autorização em uma solicitação HTTP.
Authorization: Bearer <token>
Defina o cabeçalho Content-Type
como Application/json
.
Prepare e anexe o conteúdo de solicitação emissão ou apresentação ao corpo da solicitação.
Envie a solicitação para a API REST do Serviço de Solicitação.
A API do Serviço de Solicitação retorna um código de status HTTP 201 Created
em uma chamada bem-sucedida. Se a chamada à API retornar um erro, verifique a documentação de referência de erro .
Exemplo de solicitação de emissão
O exemplo a seguir demonstra uma solicitação de emissão de credenciais verificáveis. Para obter informações sobre o conteúdo, confira especificação de emissão da API REST de Serviço de Solicitação.
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createIssuanceRequest
Content-Type: application/json
Authorization: Bearer <token>
{...JSON payload...}
Solicitação de emissão usando o fluxo de atestado idTokenHint
:
{
"authority": "did:web:verifiedid.contoso.com",
"callback": {
"url": "https://contoso.com/api/issuer/issuanceCallback",
"state": "de19cb6b-36c1-45fe-9409-909a51292a9c",
"headers": {
"api-key": "OPTIONAL API-KEY for CALLBACK EVENTS"
}
},
"registration": {
"clientName": "Verifiable Credential Expert Sample"
},
"type": "VerifiedCredentialExpert",
"manifestUrl": "https://verifiedid.did.msidentity.com/v1.0/00001111-aaaa-2222-bbbb-3333cccc4444/verifiableCredentials/contracts/VerifiedCredentialExpert1",
"pin": {
"value": "3539",
"length": 4
},
"claims": {
"given_name": "Megan",
"family_name": "Bowen"
}
}
Solicitação de emissão com o fluxo de atestado idTokenHint
que define a data de expiração:
{
"authority": "did:web:verifiedid.contoso.com",
"callback": {
"url": "https://contoso.com/api/issuer/issuanceCallback",
"state": "de19cb6b-36c1-45fe-9409-909a51292a9c",
"headers": {
"api-key": "OPTIONAL API-KEY for CALLBACK EVENTS"
}
},
"registration": {
"clientName": "Verifiable Credential Expert Sample"
},
"type": "VerifiedCredentialExpert",
"manifestUrl": "https://verifiedid.did.msidentity.com/v1.0/00001111-aaaa-2222-bbbb-3333cccc4444/verifiableCredentials/contracts/VerifiedCredentialExpert1",
"pin": {
"value": "3539",
"length": 4
},
"claims": {
"given_name": "Megan",
"family_name": "Bowen"
},
"expirationDate": "2024-12-31T23:59:59.000Z"
}
Para obter o código completo, consulte um dos seguintes exemplos de código:
Exemplo de solicitação de apresentação
O exemplo a seguir demonstra uma solicitação de apresentação de credenciais verificáveis. Para obter informações sobre o conteúdo, confira especificação de apresentação da API REST de Serviço de Solicitação.
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createPresentationRequest
Content-Type: application/json
Authorization: Bearer <token>
{...JSON payload...}
Solicitação de apresentação para uma credencial com um determinado tipo e emissor:
{
"authority": "did:web:verifiedid.contoso.com",
"callback": {
"url": "https://contoso.com/api/verifier/presentationCallback",
"state": "92d076dd-450a-4247-aa5b-d2e75a1a5d58",
"headers": {
"api-key": "OPTIONAL API-KEY for CALLBACK EVENTS"
}
},
"registration": {
"clientName": "Veritable Credential Expert Verifier"
},
"includeReceipt": true,
"requestedCredentials": [
{
"type": "VerifiedCredentialExpert",
"purpose": "So we can see that you a veritable credentials expert",
"acceptedIssuers": [
"did:web:verifiedid.contoso.com"
],
"configuration": {
"validation": {
"allowRevoked": true,
"validateLinkedDomain": true
}
}
}
]
}
Solicitação de apresentação com restrições de declarações:
{
"authority": "did:web:verifiedid.contoso.com",
"includeReceipt": false,
"registration": {
"clientName": "Contoso Job Application Center",
"purpose": "Provide proof of attended courses"
},
"callback": {
"url": "https://contoso.com/api/verifier/presentationCallback",
"state": "92d076dd-450a-4247-aa5b-d2e75a1a5d58",
"headers": {
"api-key": "OPTIONAL API-KEY for CALLBACK EVENTS"
}
},
"requestedCredentials": [
{
"type": "FabrikamCourseCertification",
"acceptedIssuers": [ "did:web:learn.fabrikam.com" ],
"constraints": [
{
"claimName": "courseCode",
"values": ["FC100", "FC110", "FC150"],
},
{
"claimName": "courseTitle",
"contains": "network",
}
],
"configuration": {
"validation": {
"allowRevoked": false,
"validateLinkedDomain": true
}
}
}
]
}
Solicitação de apresentação com o FaceCheck. Ao usar o FaceCheck, o includeReceipt
deve ser falso, pois não há suporte para recibo.
{
"authority": "did:web:verifiedid.contoso.com",
"includeReceipt": false,
"registration": {
"clientName": "Contoso Job Application Center",
"purpose": "Provide proof of attended courses"
},
"callback": {
"url": "https://contoso.com/api/verifier/presentationCallback",
"state": "92d076dd-450a-4247-aa5b-d2e75a1a5d58",
"headers": {
"api-key": "OPTIONAL API-KEY for CALLBACK EVENTS"
}
},
"requestedCredentials": [
{
"type": "VerifiedEmployee",
"acceptedIssuers": [ "did:web:learn.contoso.com" ],
"configuration": {
"validation": {
"allowRevoked": false,
"validateLinkedDomain": true,
"faceCheck": {
"sourcePhotoClaimName": "photo",
"matchConfidenceThreshold": 70
}
}
}
}
]
}
Para obter o código completo, consulte um dos seguintes exemplos de código:
Eventos de retorno de chamada
O conteúdo da solicitação contém os pontos de extremidade do retorno de chamada emissão e apresentação. O ponto de extremidade faz parte do aplicativo Web e deve estar disponível publicamente por meio do protocolo HTTPS. A API de Solicitação de Serviço chama seu endpoint para informar seu aplicativo sobre determinados eventos. Por exemplo, esses eventos podem ser quando um usuário examina o código QR, usa o link profundo para o aplicativo autenticador ou conclui o processo de apresentação.
O diagrama a seguir descreve a chamada que seu aplicativo faz para a API REST do Serviço de Solicitação e os callbacks para seu aplicativo.
Configure seu endpoint para ouvir solicitações HTTP POST recebidas. O seguinte snippet de código demonstra como tratar a solicitação HTTP de retorno de chamada de emissão e atualizar a interface do usuário em conformidade:
Não aplicável. Escolha uma das outras linguagens de programação.
[HttpPost]
public async Task<ActionResult> IssuanceCallback()
{
try
{
string content = new System.IO.StreamReader(this.Request.Body).ReadToEndAsync().Result;
_log.LogTrace("callback!: " + content);
JObject issuanceResponse = JObject.Parse(content);
// More code here
if (issuanceResponse["code"].ToString() == "request_retrieved")
{
var cacheData = new
{
status = "request_retrieved",
message = "QR Code is scanned. Waiting for issuance...",
};
_cache.Set(state, JsonConvert.SerializeObject(cacheData));
// More code here
}
}
Para ver o código completo, confira o código de emissão e apresentação no repositório GitHub.
mainApp.app.post('/api/issuer/issuance-request-callback', parser, async (req, res) => {
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
requestTrace( req );
console.log( body );
var issuanceResponse = JSON.parse(body.toString());
var message = null;
if ( issuanceResponse.code == "request_retrieved" ) {
message = "QR Code is scanned. Waiting for issuance to complete...";
}
if ( issuanceResponse.code == "issuance_successful" ) {
message = "Credential successfully issued";
}
if ( issuanceResponse.code == "issuance_error" ) {
message = issuanceResponse.error.message;
}
// More code here
res.send()
});
res.send()
})
@app.route("/api/issuer/issuance-request-callback", methods = ['POST'])
def issuanceRequestApiCallback():
if request.headers['api-key'] != apiKey:
return Response( jsonify({'error':'api-key wrong or missing'}), status=401, mimetype='application/json')
issuanceResponse = request.json
if issuanceResponse["code"] == "request_retrieved":
cacheData = {
"status": issuanceResponse["code"],
"message": "QR Code is scanned. Waiting for issuance to complete..."
}
cache.set( issuanceResponse["state"], json.dumps(cacheData) )
return ""
if issuanceResponse["code"] == "issuance_successful":
cacheData = {
"status": issuanceResponse["code"],
"message": "Credential successfully issued"
}
cache.set( issuanceResponse["state"], json.dumps(cacheData) )
return ""
if issuanceResponse["code"] == "issuance_error":
cacheData = {
"status": issuanceResponse["code"],
"message": issuanceResponse["error"]["message"]
}
cache.set( issuanceResponse["state"], json.dumps(cacheData) )
return ""
return ""
@RequestMapping(value = "/api/issuer/issue-request-callback", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
public ResponseEntity<String> issueRequestCallback( HttpServletRequest request
, @RequestHeader HttpHeaders headers
, @RequestBody String body ) {
ObjectMapper objectMapper = new ObjectMapper();
try {
if ( !request.getHeader("api-key").equals(apiKey) ) {
lgr.info( "api-key wrong or missing" );
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body( "api-key wrong or missing" );
}
JsonNode presentationResponse = objectMapper.readTree( body );
String code = presentationResponse.path("code").asText();
ObjectNode data = null;
if ( code.equals( "request_retrieved" ) ) {
data = objectMapper.createObjectNode();
data.put("message", "QR Code is scanned. Waiting for issuance to complete..." );
}
if ( code.equals("issuance_successful") ) {
data = objectMapper.createObjectNode();
data.put("message", "Credential successfully issued" );
}
if ( code.equals( "issuance_error" ) ) {
data = objectMapper.createObjectNode();
data.put("message", presentationResponse.path("error").path("message").asText() );
}
if ( data != null ) {
data.put("status", code );
cache.put( presentationResponse.path("state").asText(), objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data) );
}
} catch (java.io.IOException ex) {
ex.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body( "Technical error" );
}
return ResponseEntity.ok().body( "{}" );
}
Para ver o código completo, confira o código de emissão e apresentação no repositório GitHub.
Próximas etapas
Saiba mais sobre estas especificações: