Microsoft Entra External ID
Authentication

Microsoft Entra External ID credential management API reference

In brief

Microsoft Entra External ID now documents an API that lets applications list, register, and delete signed-in customers’ passkeys using delegated access tokens.

What Entra admins need to know

To use the API, provision its service principal manually and grant the application the required delegated permission; app-only tokens aren't supported.

This editorial summary was generated by AI from the documentation changes. Verify important details in the full Microsoft Learn article.

Documentation change

The comparison below shows only the changed extract. Use the full-page view for complete context.

new file mode 100644

title: Microsoft Entra External ID credential management API reference description: Use the Microsoft Entra External ID credential management API to let signed-in customers list, register, and delete their passkeys. author: mmacy-msft manager: dougeby ms.author: marshmacy ms.service: identity-platform ms.subservice: external ms.topic: reference ms.date: 07/23/2026 ai-usage: ai-assisted ms.custom: msecd-doc-authoring-1017 #Customer intent: As an identity developer, I want to learn how to integrate the credential management API into my customer-facing app so that customers can list, register, and delete their own credential methods.

Microsoft Entra External ID credential management API reference

[!INCLUDE applies-to-external-only]

The Microsoft Entra External ID credential management API lets your application give signed-in customers a self-service flow for listing, registering, and deleting passkeys. Your application owns the client experience and calls the API on the customer's behalf.

The credential management API complements Microsoft Entra native authentication, where your application hosts the sign-in experience instead of delegating it to a browser. Use the credential management API after a customer signs in.

Successful resource responses use HAL+JSON (application/hal+json). Activation requests use JSON (application/json), delete success responses have no body, and errors use JSON.

Prerequisites

Configure credential management API access

Before your app can acquire access tokens for the credential management API, provision the API's service principal and add a delegated permission:

  1. Provision the credential management API's service principal in your tenant. The credential management API is a Microsoft-published resource. Until automatic provisioning is available, you must create its service principal manually. In Graph Explorer, sign in as an administrator of the tenant where you want to provision it, then run:

    POST https://graph.microsoft.com/v1.0/servicePrincipals
    Content-Type: application/json
    
    {
      "appId": "6bf38b3c-a70f-49aa-a1d9-10e4cc74dde9"
    }
    
  2. Add a delegated permission to your app for the credential management API. For passkeys, the least-privileged choice is Me.UserAuthenticationMethod.Passkey.Read (read) or Me.UserAuthenticationMethod.Passkey.ReadWrite (write). See Authentication and authorization for the full list of accepted permissions. Grant admin consent if your tenant requires it.

Authentication and authorization

The credential management API acts on behalf of a signed-in customer. Every endpoint URL has the form https://{tenant-subdomain}.ciamlogin.com/{tenant-id}/api/v1.0/me/..., where {tenant-subdomain} is your external tenant's subdomain, {tenant-id} is your external tenant's ID, and me is the customer whose access token you send with the request. Your application:

  1. Signs the customer in.
  2. Obtains an access token for the credential management API.
  3. Includes that token on every call.

Without a signed-in customer, me has no meaning and the call can't be authenticated. If you've worked with Microsoft Graph or other Microsoft Entra REST APIs, this follows the same model: standard OAuth 2.0 with delegated permissions.

Include the access token in the standard HTTP Authorization header on every request:

Authorization: Bearer <access_token>

Acquire a delegated access token

Use a delegated access token to call the credential management API on behalf of the signed-in customer. The API rejects app-only tokens, including tokens acquired through the client credentials flow. When the customer signs in, request one of the permissions listed in Required permissions. The requested permission determines the token audience; you don't set the audience separately.

The following diagram shows how a call is authenticated, from signing the customer in to calling the API with the resulting token.

:::image type="content" source="media/reference-credential-management-api/authentication-authorization-flow.png" alt-text="Sequence diagram that shows your app signing the customer in to get an access token for the credential management API, then calling the API with that token while Microsoft Entra External ID validates it." lightbox="media/reference-credential-management-api/authentication-authorization-flow.png":::

Acquire the delegated token through the Microsoft Entra sign-in flow that applies to your application:

Pass the resulting token as Authorization: Bearer <access_token> on every credential management API call.

Required permissions

All permissions are delegated permissions exposed by the credential management API. Add one to your app registration and request it when you sign the customer in. Because this release supports passkeys only, the passkey-scoped permissions are the least-privileged choice.

Operation Accepted delegated permissions (least privileged first)
Read (for example, listing the customer's passkeys) Me.User.Read, Me.UserAuthenticationMethod.Passkey.Read, Me.UserAuthenticationMethod.Read, Me.UserAuthenticationMethod.Passkey.ReadWrite, Me.UserAuthenticationMethod.ReadWrite
Write (for example, registering or deleting a passkey) Me.UserAuthenticationMethod.Passkey.ReadWrite, Me.UserAuthenticationMethod.ReadWrite

Application-only access (the client credentials flow) isn't supported.

Continuation token

When you call a multi-step operation such as registering a passkey, the credential management API returns a continuation token in the response. This token uniquely identifies the current flow and lets Microsoft Entra maintain state across its endpoints. Include the token in every subsequent request in that flow. It's valid only for a limited time and can only be used for the subsequent requests within the same flow.

Supported credential methods

In this release, the credential management API supports one type of credential method:

Method URL segment ({type}) Description
Passkey fido A phishing-resistant credential based on the WebAuthn standard.

Other credential methods, such as software OATH one-time passcodes, email, phone, and recovery methods, aren't supported by this API yet.

List user credential methods

Returns the credential methods the signed-in customer has currently registered, along with the method types they can still register. For example, your app calls this endpoint when rendering a page where the customer can review their existing passkeys and start registering a new one.

The following sequence diagram shows the list flow.

:::image type="content" source="media/reference-credential-management-api/list-credential-methods.png" alt-text="Sequence diagram that shows your app calling the list endpoint with the customer's access token and Microsoft Entra External ID returning the registered and available-to-register methods in HAL+JSON." lightbox="media/reference-credential-management-api/list-credential-methods.png":::

See Authentication and authorization for the access token your application needs to call this endpoint. This endpoint accepts any of the read or write permissions listed in Required permissions.

HTTP request

GET https://{tenant-subdomain}.ciamlogin.com/{tenant-id}/api/v1.0/me/methods

{tenant-subdomain} in the URL is your external tenant's subdomain, for example contoso in contoso.ciamlogin.com.

Sample request:

GET https://contoso.ciamlogin.com/8f1c8e2a-1234-4abc-9876-1f1e1d1c1b1a/api/v1.0/me/methods HTTP/1.1
Host: contoso.ciamlogin.com
Authorization: Bearer <access_token>

Request parameters

Path parameters:

NameRequiredDescription
tenant-idYesYour external tenant identifier, either the tenant ID (GUID) or a verified domain. Use a tenant-specific value, not common, client, organizations, or consumers. A GUID value must match the tenant in the access token.

Request headers:

NameRequiredValue
AuthorizationYesBearer <access_token>

This endpoint doesn't perform Accept-header content negotiation. The response is always application/hal+json regardless of the Accept header value.

Success response

Here's an example of a successful response:

HTTP/1.1 200 OK
Content-Type: application/hal+json
{
  "_embedded": {
    "methods": [
      {
        "aaGuid": "<authenticator-aaguid>",
        "attestationLevel": "notAttested",
        "model": "Windows Hello VBS Hardware Authenticator",
        "passkeyType": "deviceBound",
        "displayName": "<display-name>",
        "id": "<credential-id-1>",
        "type": "fido",
        "createdDateTime": "<timestamp>",
        "_links": {
          "self": {
            "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-1}",
            "name": "self"
          },
          "delete": {
            "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-1}",
            "name": "delete"
          }
        }
      },
      {
        "aaGuid": "<authenticator-aaguid>",
        "attestationCertificates": [
          "<certificate-thumbprint>"
        ],
        "attestationLevel": "attested",
        "model": "YubiKey 5 FIPS Series with NFC",
        "passkeyType": "deviceBound",
        "displayName": "<display-name>",
        "id": "<credential-id-2>",
        "type": "fido",
        "createdDateTime": "<timestamp>",
        "_links": {
          "self": {
            "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-2}",
            "name": "self"
          },
          "delete": {
            "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-2}",
            "name": "delete"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "/{tenant-id}/api/v1.0/me/methods",
      "name": "self"
    },
    "enroll": [
      {
        "href": "/{tenant-id}/api/v1.0/me/methods/fido",
        "name": "fido"
      }
    ],
    "methods": [
      {
        "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-1}",
        "name": "<credential-id-1>"
      },
      {
        "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id-2}",
        "name": "<credential-id-2>"
      }
    ]
  }
}

Follow the returned HAL links instead of constructing operation URLs from identifiers.

PropertyTypeRequiredDescription
_embedded.methodsarray of objectsYesRegistered credential methods. The array is present even when the customer has no registered methods.
_links.selfobjectYesLink to the credential-method collection.
_links.enrollarray of objectsNoEnrollment links, one for each credential type the customer can register.
_links.methodsarray of objectsNoLinks to the customer's registered methods.

Registered passkey properties

Authenticator-specific properties can vary or be absent. For example, a nonattested passkey can omit attestationCertificates, while an attested device-bound passkey can include certificate values.

PropertyTypeRequiredDescription
idstringYesDurable, case-sensitive base64url identifier for the registered passkey.
typestringYesCredential method type. The value is fido.
createdDateTimestringNoTime when the passkey was registered.
lastUsedDateTimestringNoTime when the passkey was last used.
aaGuidstringNoAuthenticator Attestation GUID (AAGUID), when returned by the authenticator.
attestationCertificatesarray of stringsNoAttestation certificate values, when the authenticator provides attestation.
attestationLevelstringNoAttestation level reported for the authenticator.
modelstringNoAuthenticator model.
passkeyTypestringNoPasskey classification reported by the service.
displayNamestringNoCustomer-provided passkey name.
_linksobjectYesself and delete links for the registered passkey.

This endpoint can return 400, 401, or 403. For the shared envelope and caller actions, see Error responses.

Provisioning

Provisioning a passkey for the signed-in customer takes two HTTP calls. First, your application calls the begin registration endpoint, which returns WebAuthn creation options, a continuation token, and an activate link. The customer's authenticator uses the creation options to create a new passkey. Your application then calls the activate endpoint with the result to complete registration. Start this flow when the customer chooses to add a passkey in your application's credential-management experience.

The following sequence diagram shows the two-step provisioning flow.

:::image type="content" source="media/reference-credential-management-api/provision-passkey.png" alt-text="Sequence diagram that shows begin registration returning a WebAuthn challenge and continuation token, the authenticator creating the passkey, and the activate call registering the passkey with Microsoft Entra External ID." lightbox="media/reference-credential-management-api/provision-passkey.png":::

See Authentication and authorization for the access token your application needs to call these endpoints. Both calls require a write permission, Me.UserAuthenticationMethod.Passkey.ReadWrite or Me.UserAuthenticationMethod.ReadWrite (see Required permissions).

Step 1: Begin registration

HTTP request

POST https://{tenant-subdomain}.ciamlogin.com/{tenant-id}/api/v1.0/me/methods/{type}

{tenant-subdomain} in the URL is your external tenant's subdomain, for example contoso in contoso.ciamlogin.com.

Sample request:

POST https://contoso.ciamlogin.com/8f1c8e2a-1234-4abc-9876-1f1e1d1c1b1a/api/v1.0/me/methods/fido HTTP/1.1
Host: contoso.ciamlogin.com
Authorization: Bearer <access_token>

Request parameters

Path parameters:

NameRequiredDescription
tenant-idYesYour external tenant identifier, either the tenant ID (GUID) or a verified domain. Use a tenant-specific value, not common, client, organizations, or consumers. A GUID value must match the tenant in the access token.
typeYesThe credential method type to register. Currently only fido (passkey) is supported.

Request headers:

NameRequiredValue
AuthorizationYesBearer <access_token>

This endpoint doesn't take a request body. All inputs are carried in the URL path and the Authorization header.

Success response

Here's an example of a successful response:

HTTP/1.1 202 Accepted
Content-Type: application/hal+json
{
  "publicKey": {
    "rp": {
      "id": "<relying-party-id>",
      "name": "Microsoft"
    },
    "user": {
      "id": "<user-handle>",
      "name": "<user-name>",
      "displayName": "<user-display-name>"
    },
    "challenge": "<challenge>",
    "pubKeyCredParams": [
      {
        "type": "public-key",
        "alg": -7
      },
      {
        "type": "public-key",
        "alg": -257
      }
    ],
    "timeout": 0,
    "excludeCredentials": [
      {
        "type": "public-key",
        "id": "<excluded-credential-id>",
        "transports": []
      }
    ],
    "authenticatorSelection": {
      "authenticatorAttachment": "cross-platform",
      "requireResidentKey": true,
      "userVerification": "required"
    },
    "attestation": "direct",
    "extensions": {
      "hmacCreateSecret": true,
      "enforceCredentialProtectionPolicy": true,
      "credentialProtectionPolicy": "userVerificationOptional"
    }
  },
  "challengeTimeout": "<timestamp>",
  "id": "<registration-id>",
  "type": "fido",
  "continuationToken": "<continuation-token>",
  "state": "interactionRequired",
  "action": "activate",
  "_links": {
    "self": {
      "href": "/{tenant-id}/api/v1.0/me/methods/fido/{registration-id}",
      "name": "self"
    },
    "activate": {
      "href": "/{tenant-id}/api/v1.0/me/methods/fido/{registration-id}/activate",
      "name": "activate"
    }
  }
}

The response has the following properties:

PropertyTypeRequiredDescription
idstringYesTransient identifier for the in-progress registration.
typestringYesCredential method type. The value is fido.
publicKeyobjectNoWebAuthn credential creation options for the client ceremony.
challengeTimeoutstringNoTime when the challenge expires.
statestringYesFlow state. The value is interactionRequired.
actionstringYesNext action. The value is activate.
continuationTokenstringYesOpaque state that must be returned during activation.
_linksobjectYesself and activate links for the in-progress registration.

The publicKey object aligns with the common fields in webauthnPublicKeyCredentialCreationOptions. The service can also preserve additional WebAuthn properties for forward compatibility.

PropertyTypeRequiredDescription
rpobjectNoRelying-party information.
userobjectNoUser information for the registration ceremony.
challengestringNoBase64url-encoded WebAuthn challenge.
pubKeyCredParamsarray of objectsNoSupported public-key credential types and algorithms.
timeoutintegerNoCeremony timeout in milliseconds.
excludeCredentialsarray of objectsNoExisting credentials that the authenticator should exclude.
authenticatorSelectionobjectNoAuthenticator selection requirements.
attestationstringNoRequested attestation conveyance preference.
hintsarray of stringsNoOptional authenticator hints.
extensionsobjectNoWebAuthn extension inputs.

Pass publicKey to the customer's authenticator to create the passkey. Preserve continuationToken exactly, and follow _links.activate.href for the activation request.

The begin registration endpoint can return 400, 401, 403, or 500. For the shared envelope and caller actions, see Error responses.

Step 2: Activate the passkey

HTTP request

POST https://{tenant-subdomain}.ciamlogin.com/{tenant-id}/api/v1.0/me/methods/{type}/{id}/activate

{tenant-subdomain} in the URL is your external tenant's subdomain, for example contoso in contoso.ciamlogin.com.

Sample request:

POST https://contoso.ciamlogin.com/8f1c8e2a-1234-4abc-9876-1f1e1d1c1b1a/api/v1.0/me/methods/fido/{registration-id}/activate HTTP/1.1
Host: contoso.ciamlogin.com
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "continuationToken": "<continuation-token>",
  "displayName": "My laptop",
  "publicKeyCredential": {
    "id": "<credential-id>",
    "attestationObject": "<attestation-object>",
    "clientDataJSON": "<client-data-json>"
  }
}

Request parameters

Path parameters:

NameRequiredDescription
tenant-idYesYour external tenant identifier, either the tenant ID (GUID) or a verified domain. Use a tenant-specific value, not common, client, organizations, or consumers. A GUID value must match the tenant in the access token.
typeYesThe credential method type being registered. Currently only fido (passkey) is supported. Must match the value used in the begin registration step.
idYesThe in-progress registration identifier returned by the begin registration step. Take it from _links.activate.href in the begin registration response rather than constructing it yourself.

Request headers:

NameRequiredValue
AuthorizationYesBearer <access_token>
Content-TypeYesapplication/json

Request body

A JSON object with the following fields:

NameTypeRequiredDescription
continuationTokenstringYesThe opaque token returned by the begin registration response. It carries the server-side state of the in-progress registration so Microsoft Entra can resume exactly where it left off. Treat it as opaque and don't parse or modify it. See Continuation token.
displayNamestringNoA friendly name the customer gives the passkey, for example My laptop.
publicKeyCredentialobjectNoWebAuthn credential data produced by the customer's authenticator.

The publicKeyCredential value is derived from the WebAuthn PublicKeyCredential. The API flattens the credential ID, attestation response, and extension results into the following properties:

NameTypeRequiredDescription
idstringNoBase64url-encoded credential identifier returned by the authenticator.
attestationObjectstringNoBase64url-encoded attestation object returned by the authenticator.
clientDataJSONstringNoBase64url-encoded client data returned by the authenticator.
clientExtensionResultsstringNoSerialized WebAuthn client extension results.

Success response

Here's an example of a successful response:

HTTP/1.1 201 Created
Content-Type: application/hal+json
{
  "aaGuid": "<authenticator-aaguid>",
  "attestationLevel": "notAttested",
  "model": "Windows Hello VBS Hardware Authenticator",
  "passkeyType": "deviceBound",
  "displayName": "<display-name>",
  "id": "<credential-id>",
  "type": "fido",
  "createdDateTime": "<timestamp>",
  "_links": {
    "self": {
      "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id}",
      "name": "self"
    },
    "delete": {
      "href": "/{tenant-id}/api/v1.0/me/methods/fido/{credential-id}",
      "name": "delete"
    }
  }
}

The activation response has the following properties:

PropertyTypeRequiredDescription
idstringYesIdentifier returned for the registered passkey.
typestringYesCredential method type. The value is fido.
createdDateTimestringNoTime when registration completed.
displayNamestringNoCustomer-provided passkey name.
aaGuidstringNoAuthenticator Attestation GUID (AAGUID), when returned by the authenticator.
attestationCertificatesarray of stringsNoAttestation certificate values, when the authenticator provides attestation.
attestationLevelstringNoAttestation level reported for the authenticator.
modelstringNoAuthenticator model.
passkeyTypestringNoPasskey classification reported by the service.
_linksobjectYesself and delete links for the registered passkey.

Authenticator-specific properties can vary or be absent. The activation endpoint can return 400, 401, or 403. For the shared envelope and caller actions, see Error responses.

Delete a credential method

Permanently deletes one of the signed-in customer's registered passkeys. Use this endpoint after listing the customer's credential methods and when the customer chooses to remove a passkey in your application's credential-management flow. Deletion is immediate and can't be undone. A replacement passkey is a new credential with no relation to the deleted passkey.

The following sequence diagram shows the delete flow.

:::image type="content" source="media/reference-credential-management-api/delete-credential-method.png" alt-text="Sequence diagram that shows your app calling the delete endpoint with the customer's access token and Microsoft Entra External ID removing the passkey and returning 204 No Content." lightbox="media/reference-credential-management-api/delete-credential-method.png":::

See Authentication and authorization for the access token your application needs to call this endpoint. This endpoint requires a write permission, Me.UserAuthenticationMethod.Passkey.ReadWrite or Me.UserAuthenticationMethod.ReadWrite (see Required permissions).

HTTP request

DELETE https://{tenant-subdomain}.ciamlogin.com/{tenant-id}/api/v1.0/me/methods/{type}/{id}

{tenant-subdomain} in the URL is your external tenant's subdomain, for example contoso in contoso.ciamlogin.com.

Sample request:

DELETE https://contoso.ciamlogin.com/8f1c8e2a-1234-4abc-9876-1f1e1d1c1b1a/api/v1.0/me/methods/fido/{id} HTTP/1.1
Host: contoso.ciamlogin.com
Authorization: Bearer <access_token>

Request parameters

Path parameters:

NameRequiredDescription
tenant-idYesYour external tenant identifier, either the tenant ID (GUID) or a verified domain. Use a tenant-specific value, not common, client, organizations, or consumers. A GUID value must match the tenant in the access token.
typeYesThe credential method type to delete. Currently only fido (passkey) is supported.
idYesThe identifier of the registered passkey to delete. Use the id (or follow the delete link) from List user credential methods, the source of truth for the customer's registered methods.

Request headers:

NameRequiredValue
AuthorizationYesBearer <access_token>

This endpoint doesn't take a request body. The passkey to delete is identified entirely by the URL.

Success response

A successful delete returns an empty response:

HTTP/1.1 204 No Content

This endpoint can return 400, 401, or 403. For the shared envelope and caller actions, see Error responses.

Error responses

Error responses use a shared JSON envelope. The following example shows the required properties:

{
  "error": {
    "code": "invalidRequest",
    "message": "<error-message>",
    "timestamp": "<timestamp>",
    "traceId": "<trace-id>",
    "correlationId": "<correlation-id>"
  }
}

The error response has the following properties:

PropertyTypeRequiredDescription
errorobjectYesError details.
error.codestringYesMachine-readable error code.
error.messagestringYesHuman-readable description of the failure. Don't use this value for programmatic branching.
error.timestampstringYesTime when the error occurred.
error.traceIdstringYesIdentifier used to trace the request.
error.correlationIdstringYesIdentifier used to correlate the request across components.
error.targetstringNoRequest element associated with the error.
error.innerErrorobjectNoAdditional error details.
clientHintsarray of stringsNoOptional response-wide hint tokens.

The following status and code combinations are confirmed for these endpoints:

HTTP statusError codeConfirmed causes
400 Bad RequestinvalidRequestMissing or malformed bearer token, invalid token signature, expired token, app-only token, insufficient delegated scope, a non-specific tenant route, or invalid registration state such as a malformed continuation token.
401 UnauthorizedinvalidRequestThe token wasn't issued for the credential management API.
403 ForbiddenforbiddenThe tenant in the request URL doesn't match the tenant in the access token.
500 Internal Server ErrorserverErrorThe begin registration endpoint received an invalid WebAuthn creation-options response from an upstream component.

The shared error model also defines unknown, invalidGrant, expiredToken, tooManyRequests, and unsupportedRedirect. Don't assume that every endpoint emits every code.

Handle confirmed errors as follows:

ResponseCaller action
400 invalidRequestUse error.message to diagnose the invalid input. Acquire a new delegated token when the token is missing, malformed, invalid, expired, app-only, or lacks an accepted scope. Use a tenant-specific route value. If registration state is invalid, restart the two-step registration flow.
401 invalidRequestRequest an accepted credential management API permission when acquiring the token, then retry the request.
403 forbiddenVerify that the {tenant-id} in the request URL matches the tenant represented by the access token.
500 serverErrorRecord traceId and correlationId for diagnostics. Don't assume that the operation succeeded.
Daily Entra.News

Get daily email updates

Get a concise summary of the latest Microsoft Entra updates delivered straight to your inbox.

Loading the secure signup form…