Microsoft Entra Verified ID zawiera interfejs API REST dla żądania usług. Ten interfejs API umożliwia wystawianie i weryfikowanie poświadczeń. W tym artykule pokazano, jak rozpocząć korzystanie z interfejsu API REST usługi żądań.
Token dostępu interfejsu API
Aplikacja musi dołączyć prawidłowy token dostępu z wymaganymi uprawnieniami, aby mogła uzyskać dostęp do interfejsu API REST usługi żądań. Tokeny dostępu wydawane przez platformę tożsamości Microsoft zawierają informacje (zakresy), które interfejs API REST usługi żądań wykorzystuje do weryfikacji wywołującego. Token dostępu gwarantuje, że obiekt wywołujący ma odpowiednie uprawnienia do wykonania żądanej operacji.
Aby uzyskać token dostępu, aplikacja musi zostać zarejestrowana na platformie tożsamości firmy Microsoft i autoryzowana przez administratora w celu uzyskania dostępu do interfejsu API REST usługi żądań. Jeśli nie zarejestrowałeś aplikacji verifiable-credentials-app, zobacz jak zarejestrować aplikację, a następnie wygenerować tajny kod aplikacji.
Uzyskiwanie tokenu dostępu
Użyj przepływu uprawnień klienta OAuth 2.0, aby uzyskać token dostępu przy użyciu platformy tożsamości Microsoft. W tym celu użyj zaufanej biblioteki. W tym samouczku użyjemy biblioteki Microsoft Authentication Library (MSAL). Biblioteka MSAL upraszcza dodawanie uwierzytelniania i autoryzacji do aplikacji, która może wywoływać bezpieczny sieciowy interfejs programowania aplikacji (API).
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();
W poprzednim kodzie podaj następujące parametry:
Parametr |
Warunek |
Opis |
Autorytet |
Wymagane |
Dzierżawca katalogu, na którym aplikacja planuje działać. Na przykład: https://login.microsoftonline.com/{your-tenant} . (Zastąp your-tenant identyfikatorem dzierżawy lub nazwą). |
Identyfikator klienta |
Wymagane |
Identyfikator aplikacji przypisany do aplikacji. Te informacje można znaleźć w witrynie Azure Portal, w której zarejestrowano aplikację. |
Sekret klienta |
Wymagane |
Tajny klucz klienta, który wygenerowałeś dla swojej aplikacji. |
Zakresy |
Wymagane |
Musi być ustawione na 3db474b9-6a0c-4840-96ac-1fceb342124f/.default . To ustawienie tworzy token dostępu z roszczeniem roli typu równego VerifiableCredential.Create.All . |
Aby uzyskać więcej informacji na temat uzyskiwania tokenu dostępu przy użyciu tożsamości aplikacji konsolowej, zobacz jeden z następujących artykułów:
Możesz również uzyskać dostęp do żądania tokenu za pomocą certyfikatu zamiast sekretu klienta.
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();
Wywoływanie interfejsu API
Aby wydać lub zweryfikować poświadczenia weryfikowalne:
Skonstruuj żądanie HTTP POST do Request Service REST API. Identyfikator dzierżawy nie jest już potrzebny w adresie URL, ponieważ jest obecny jako klauzula w tokenie dostępu.
Problem
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createIssuanceRequest
Zweryfikuj
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createPresentationRequest
Dołącz token dostępu jako token uwierzytelniający typu Bearer do nagłówka autoryzacyjnego w żądaniu HTTP.
Authorization: Bearer <token>
Ustaw nagłówek Content-Type
na wartość Application/json
.
Przygotuj i dołącz ładunek żądania wystawienia lub prezentacji do treści żądania.
Wyślij żądanie do interfejsu API REST usługi Request Service.
Interfejs API dla żądań zwraca kod stanu HTTP 201 Created
przy pomyślnym wywołaniu. Jeśli wywołanie interfejsu API zwróci błąd, zapoznaj się z dokumentacją referencyjną błędu.
Przykład prośby o wydanie
W poniższym przykładzie przedstawiono weryfikowalne żądanie wystawiania poświadczeń. Aby uzyskać informacje o ładunku, zobacz specyfikację wystawiania żądania usługi API REST.
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createIssuanceRequest
Content-Type: application/json
Authorization: Bearer <token>
{...JSON payload...}
Żądanie wystawiania przy użyciu przepływu zaświadczania 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"
}
}
Żądanie wystawienia przy użyciu procedury poświadczenia idTokenHint
, która ustawia termin wygaśnięcia:
{
"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"
}
Pełny kod można znaleźć w jednym z następujących przykładów kodu:
Przykład żądania prezentacji
W poniższym przykładzie pokazano weryfikowalne żądanie prezentacji poświadczeń. Aby uzyskać informacje o ładunku, zobacz specyfikację prezentacji interfejsu API REST usługi Request Service.
POST https://verifiedid.did.msidentity.com/v1.0/verifiableCredentials/createPresentationRequest
Content-Type: application/json
Authorization: Bearer <token>
{...JSON payload...}
Prośba o przedstawienie poświadczenia określonego typu i przez konkretnego wystawcę.
{
"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
}
}
}
]
}
Prośba o prezentację z ograniczeniami roszczeń :
{
"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
}
}
}
]
}
Prośba o prezentację za pomocą FaceCheck. Korzystając z FaceCheck, includeReceipt
trzeba ustawić na fałszywe, ponieważ potwierdzenie nie jest wtedy obsługiwane.
{
"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
}
}
}
}
]
}
Pełny kod można znaleźć w jednym z następujących przykładów kodu:
Zdarzenia zwrotne
Treść żądania zawiera punkt końcowy zwrotny dla wystawiania i prezentacji. Punkt końcowy jest częścią aplikacji internetowej i powinien być publicznie dostępny za pośrednictwem protokołu HTTPS. API Request Service wywołuje Twój endpoint, aby poinformować aplikację o pewnych zdarzeniach. Na przykład takie zdarzenia mogą występować, gdy użytkownik skanuje kod QR, używa linku głębokiego do aplikacji authenticator lub kończy proces prezentacji.
Na poniższym diagramie jest opisane wywołanie, które Twoja aplikacja wykonuje do interfejsu API REST usługi żądań, oraz wywołania zwrotne do Twojej aplikacji.
Skonfiguruj punkt końcowy do nasłuchiwania przychodzących żądań HTTP POST. Poniższy fragment kodu pokazuje, jak obsługiwać żądanie HTTP dotyczące wywołania zwrotnego dla wydania oraz jak odpowiednio zaktualizować interfejs użytkownika.
Nie dotyczy. Wybierz jeden z innych języków programowania.
[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
}
}
Aby uzyskać pełny kod, zobacz kod wystawienia i kod prezentacji w repozytorium 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( "{}" );
}
Aby uzyskać pełny kod, zobacz kod wystawienia i kod prezentacji w repozytorium GitHub.
Następne kroki
Dowiedz się więcej o tych specyfikacjach: