{
  "@context": [
    "https://www.w3.org/ns/credentials/v2"
  ],
  "type": [
    "VerifiableCredential",
    "BlogPostCredential"
  ],
  "id": "urn:uuid:5a5d3e9b-b86d-4b85-85ae-162116bd30ab",
  "issuer": "did:webvh:QmTVQnV3qGxWzWmnmWJAy1zkYswgbUmE95K5qodmAizVfr:mjendza.net",
  "validFrom": "2026-03-15T13:35:32Z",
  "credentialSubject": {
    "title": "Entra External ID for Customers - Native Authentication part 1",
    "author": "Mateusz Jendza",
    "body": "![native-title](/images/external-id/native-main.jpg)\r\n## History\r\n- 2024-06-02 - Initial version\r\n- 2025-03-03 - Updated Summary\r\n\r\n## TL;DR\r\nWith Azure AD B2C we can authenticate users via a browser-based feature - we run the OpenID Connect flow in the browser, authenticate the user via the Authorization Code flow, and get the token. \r\n\r\nBut what if we want to stay with the mobile application? On the Desktop application? With Entra External ID we can use the native authentication flow.\r\n\r\nPS> Native Authentication is in preview mode - it is not for production use!\r\n\r\n### DEMO\r\nSign-UP via Web: https://portal.factorlabs.pl/external-id/ and Sign-In via: https://profile.factorlabs.pl/native\r\n\r\n## Solution Overview\r\nThe Native Auth API lacks CORS, so I needed to use a Proxy API to make a demo with the web application.\r\nThe proxy API is stateless; the response is returned to the client's browser. This is only a DEMO and sample. Please consider using a secure way to store the ContinuationToken (Secure and HttpOnly Cookie?).\r\n\r\nToday, Entra External ID does not support MFA for 'CIAM' users. With the MFA, there will be an additional step in the flow (maybe an extra API call?) - we will see in the future.\r\n## Code Snippets\r\n\r\nInitialize the flow - share username (email address) to start the flow:\r\n```csharp\r\nprivate string _clientId = \"71e0abe3-1111-2222-5555-acf032d43284\";\r\nprivate string _tenantNativeUrl = \"https://my-tenant.ciamlogin.com/my-tenant.onmicrosoft.com\";\r\n\r\nvar values = new Dictionary<string, string>\r\n{\r\n    { \"client_id\", _clientId },\r\n    { \"challenge_type\", \"oob redirect\" },\r\n    { \"username\", model.Username }\r\n};\r\n\r\nvar content = new FormUrlEncodedContent(values);\r\nvar response = await _httpClient.PostAsync(\r\n    $\"{_tenantNativeUrl}/oauth2/v2.0/initiate\",\r\n    content);\r\n```\r\nAs a result, we get the continuationToken. We will use it with the next REST Call to get the code.\r\n\r\nNext API request is with `challenge_type`: `oob` - so we want to authenticate the user with OTP (One Time Password). In my opinion in a world, where we can log everything and everywhere, Terminating SSL traffic - is important to use a 'temporary' password, is safer than sharing the password.\r\n\r\n```csharp\r\nvar values = new Dictionary<string, string>\r\n{\r\n    { \"client_id\", _clientId },\r\n    { \"challenge_type\", $\"oob redirect\" },\r\n    { \"continuation_token\", model.ContinuationToken }\r\n};\r\n\r\nvar content = new FormUrlEncodedContent(values);\r\nvar response = await _httpClient.PostAsync(\r\n    $\"{_tenantNativeUrl}/oauth2/v2.0/challenge\",\r\n    content);\r\n```\r\n\r\nWe are asking for Scope `openid` with the `grant_type`: `oob`. User needs to provide the OTP to get the token.\r\n```csharp\r\nvar values = new Dictionary<string, string>\r\n{\r\n        { \"client_id\", _clientId },\r\n        { \"continuation_token\", model.ContinuationToken },\r\n        {\"scope\", \"openid\"},\r\n        {\"grant_type\", \"oob\"},\r\n        {\"oob\", model.Password}\r\n};\r\nvar content = new FormUrlEncodedContent(values);\r\nvar response = await _httpClient.PostAsync(\r\n$\"{_tenantNativeUrl}/oauth2/v2.0/token\",\r\ncontent);\r\n```\r\n\r\n## A summary: \r\n- **We can't use OTP for account created with password.**\r\n- The context is client-id - so we run the flow for the application.\r\n- Enable the Native Authentication for your application.\r\n- Register User flow with OTP, use with the test user created with OTP (not password!) - the feature is in preview mode - so maybe some changes will be provided in the future.\r\n- FormUrlEncoded POST requests.\r\n- ContinuationToken is a key to the next step.\r\n- Needed to use Proxy API - Entra External ID is without CORS methods.\r\n- I decided to build a web application to test it. Feel free to run a test: Sign-UP via Web (Password Flow): https://portal.factorlabs.pl/external-id/ and https://profile.factorlabs.pl/native for Native Auth.\r\n- [MS Documentation](https://learn.microsoft.com/en-us/entra/identity-platform/reference-native-authentication-email-otp)\r\n\r\n## What next?\r\nMy plan is to test SDK for [Android](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-run-native-authentication-sample-android-app) and your?",
    "datePublished": "2024-06-02",
    "url": "/post/external-id-native-auth",
    "description": "Native Authentication",
    "tags": [
      "Authentication",
      "Security",
      "External-ID",
      "CIAM"
    ]
  },
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "verificationMethod": "did:key:z6MksoqpqENZmzzA4nhCPkfcbWtRHVegGV38Yqu2arRc5Er2#z6MksoqpqENZmzzA4nhCPkfcbWtRHVegGV38Yqu2arRc5Er2",
    "created": "2026-03-15T13:35:32Z",
    "proofPurpose": "assertionMethod",
    "proofValue": "z3JJeqdSGxfruxBn5LhMmsDMJ9dsEr7y9YSy4oGCqwYPJPS3DawU5iaFzrhbamgKQ3Wi7Zoeia4YqnLft34Nqo7Pa"
  }
}