Custom Domains for Azure AD B2C using Azure AppServices

[DISCLAIMER: you should not use this solution anymore as B2C now uses Azure Front Door for custom domains]

A common request to Microsoft is having a custom domain when using Azure AD B2C for your CIAM solution. The host of the endpoint for the OIDC/OAuth2 authentication is and even though it doesn’t say Microsoft all over it, brand sensitive companies want it to be, or similar.

Microsoft has Custom Domain for B2C currently in private preview, but enrolling into that preview, if you are invited, takes time. If you need Custom Domain asap, perhaps to impress in a demo or for technical reasons, you can achieve this via proxying B2C via Azure App Services. The trick is configuring your AppService to do url rewrite from to Since Azure AppServices supports configuring a Custom Domain, you can achieve what you want.

Deploy your Azure AppService

The documentation for deploying an Azure AppService that supports Custom Domains can be found here It is straight forward and all you need to take into consideration is:

  • Make sure the pricing tier you select for the AppService Plan supports Custom Domain. The Free F1 doesn’t.
  • Select OS Type Windows as you need IIS
  • Select Runtime Stack ASP.Net v4.7.

Configuring the URL Rewrite rules

To make your AppService act like a reverse proxy, you need to edit the Web.Config file and add some rules and you need to add a file named applicationHost.xdt. The Web.Config exists in /site/wwwroot and applicationHost.xdt need to be in /site (ie, one level up). Adding these files is all you need to do. You need no other deployment to the AppService.


The full Web.Config file you need exists in github here. The default rewrite rule just takes all urls and rewrites then to be In your backend code, the hostname of the AppService will be passed in the x-original-host header.

<rule name="Proxy" stopProcessing="true">
 <match url="(.*)" />
<action type="Rewrite" url="{R:1}" />
<set name="HTTP_X_UNPROXIED_URL" value="" />
 <set name="HTTP_X_ORIGINAL_HOST" value="{HTTP_HOST}" />
<set name="HTTP_ACCEPT_ENCODING" value="" />

If you look in the complete Web.Config file, you’ll see that you have three outbound rules too to handle the rewrite of cookie domain names so that any cookie B2C emits for gets passed to Otherwise SSO would break.


You don’t need to edit the applicationHost.xdt file, but one setting is really important and that is


since without this setting, the URL rewriting is just a little too eager and will rewrite the redirect_uri parameter on the way out of the reverse proxy and your app would break as the redirect back would fail.


If you use B2C’s feature of hosting your own HTML pages, then you need to update your CORS rules, since it is not that requests your HTML page anymore but

If you are using Azure Blob Storage for hosting your HTML, you have the documentation here You need to add (or whatever your domain is called). If you just want to test via proxying thought AppServices, then add

Deploying your config to Azure AppServices

There are many ways to deploy the files, but if you like an easy and fast way, you can revert to using good old FTP. You will find the FTP credentials in the file you can download from Get publish profile menu item in In your FTP client, make sure you select passive mode as that is required.

You need to upload

  • Web.Config –> /site/wwwroot
  • applicationHost.xdt –> /site


To successfully test this, you need to complete the steps in the documentation about how to configure a custom domain for AppServices. This includes having a DNS Admin to work with you as that is the way to prove that you own the domain. To complete the configuration, you need to upload a certificate for the domain, like, but you can test it before and you just have to accept that your browser complains about the certificate.

One thing to be aware of is that the JWT token that B2C issues does not reflect the domain name you configured for AppServices. The iss claim will state the since AppService is only a reverse proxy in this solution. When you use B2C’s own Custom Policy in private preview, the iss claim will reflect that domain name.

But wait, there is more…

There is one more thing that you need to fix if you really want to use this solution from a real app and it is the result of metadata endpoint .well-known/openid-configuration. Since Azure AppServices is just a reverse proxy, the call to will go through, but it will return values in authorization_endpoint, etc, that points directly to the B2C tenant. That means that your app will use these values when it redirects for authentication and you’re back to square one.

So how do we fix this…?

We add another rule in Web.Config that reroutes requests to /.well-known/openid-configuration to an Azure Function we have deployed and let that Azure Function call B2C’s metadata endpoint and modify the response before returning it.

<rule name="RedirectWellKnownOpenidConfiguration" stopProcessing="true">          <match url="^(.*)\/.well-known\/openid-configuration(.*)$" />
 <action type="Rewrite" url="{HTTP_URL}"/>

We pass the original url for the metadata request in a query string parameter named url and pass the hostname we want to have in HTTP Header x-original-host

public static async Task<HttpResponseMessage> Run(HttpRequest req, ILogger log){
    string url = req.Query["url"];
    string originalHost = req.Headers["x-original-host"];
    string[] parts = url.Split("/".ToCharArray());
    string host = parts[2];
    HttpClient client = new HttpClient();
    HttpResponseMessage res = client.GetAsync(url).Result;
    var contents = await res.Content.ReadAsStringAsync();
    if ( res.StatusCode == HttpStatusCode.OK ) {

        dynamic json = JsonConvert.DeserializeObject(contents);
         json.authorization_endpoint = ((string)json.authorization_endpoint).Replace(host, originalHost);
        json.token_endpoint = ((string)json.token_endpoint).Replace(host, originalHost);
        json.end_session_endpoint = ((string)json.end_session_endpoint).Replace(host, originalHost);
        json.jwks_uri = ((string)json.jwks_uri).Replace(host, originalHost);
        contents = JsonConvert.SerializeObject(json);
    var content = new StringContent(contents, System.Text.Encoding.UTF8, "application/json");
    content.Headers.Add("Access-Control-Allow-Origin", "*");
    return new HttpResponseMessage(res.StatusCode) {Content = content};}

This produces the following metadata JSON response