Customize Login Pages

You can customize the New Universal Login pages by providing a page template using the Liquid template language. To learn more about Liquid, read the Introduction to Liquid on Github. You can update the ULP templates only using the Management API. This capability can only be used if the tenant has Custom Domains enabled.

Page templates let you define the content that is displayed around the Universal Login widgets (e.g., the Login box, the MFA box). The same template is used for all pages, which helps you to implement a consistent login with minimum effort.

The simplest template you can write is:

<!DOCTYPE html><html>
  <head>
    {%- auth0:head -%}
  </head>
  <body>
    {%- auth0:widget -%}
  </body></html>

The following tags must be present in the template:

  • auth0:widget - Includes the HTML for the widget that is displayed in every page (e.g., Login, Reset Password)

  • auth0:head - Includes tags that are required to render the widget

If you use the class="_widget-auto-layout" in the <body> element, the widget will be centered in the page. If you want to position it yourself, you can omit it.

Page template variables

The page templates have a set of context variables that can be used to impact how the page is rendered. This allows you to implement scenarios like:

  • Render different content depending on the Application (for example, you own two brands that need a different page design).

  • Render different content depending on the Prompt (for example, in the Login page you want to add content explaining what the application does, but in the MFA flow, you prefer to only have the MFA box).

  • Add a footer with links to the tenant's support page or email.

Available variables

The available variables are:

  • Information about the current user, for pages rendered after the user authenticates:

    • user.user_id

    • user.picture

    • user.email

    • user.email_verified

    • user.app_metadata

    • user.user_metadata

    • user.family_name

    • user.given_name

    • user.name

    • user.nickname

    • user.username

Examples

Login box + image layout

The following template will show the login box to the left, and an image to the right only for the login/signup pages. The rest of the pages will look like the default ones.

<!DOCTYPE html><html lang="{{locale}}">
  <head>
    {%- auth0:head -%}
    <style>
      body {
        background-image: url("https://images.unsplash.com/photo-1592450865877-e3a318ec3522?ixlib=rb-1.2.1&auto=format&fit=crop&w=2255&q=80");
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
      }
      .prompt-wrapper {
        position: relative;
        display: flex;
        align-items: center;
        width: 480px;
        height: 100%;
        justify-content: center;
        background-color: rgb(60,60,60);
      }
    </style>
    <title>{{ prompt.screen.texts.pageTitle }}</title>
  </head>
  <body>
    {% if prompt.name == "login" or prompt.name == "signup" %} 
        <div class="prompt-wrapper">
        {%- auth0:widget -%}
        </div>
    {% else %}
        {%- auth0:widget -%}
    {% endif %}
  </body>
</html>

Universal Login box with email username/password and image layout example

Page footers

The example below adds a gray footer with links to Privacy Policy and Terms of Services:

<!DOCTYPE html><html lang="{{locale}}">
  <head>
    {%- auth0:head -%}
    <style>
      body {
        background-image: radial-gradient(white, rgb(200, 200, 200));
      }
      .footer {
        background-color: rgb(120, 120, 120);
        position: absolute;
        bottom: 0;
        left: 0;
        padding: 16px 0; 
        width: 100%;
        color: white;
        /* Use a high z-index for future-proofing */
        z-index: 10;
      }
      .footer ul {
        text-align: center;
      }
      .footer ul li {
        display: inline-block;
        margin: 0 4px;
      }
      .footer ul li:not(:first-of-type) {
        margin-left: 0;
      }
      .footer ul li:not(:first-of-type)::before {
        content: '';
        display: inline-block;
        vertical-align: middle;
        width: 4px;
        height: 4px;
        margin-right: 4px;
        background-color: white;
        border-radius: 50%;
      }
      .footer a {
        color: white;
      }
    </style>
     <title>{{ prompt.screen.texts.pageTitle }}</title>
  </head>
  <body class="_widget-auto-layout">
    {%- auth0:widget -%}
    <footer class="footer">
      <ul>
        <li><a href="https://company.com/privacy">Privacy Policy</a></li>
        <li><a href="https://company.com/terms">Terms of Service</a></li>
      </ul>
    </footer>
  </body></html>

Universal Login box with email address/password and footers layout example

Page templates API

To set the page template, you need to use the Management API. You first need to get a Management API token with the update:branding, read:branding, delete:branding scopes. If you are using the API Explorer Application to generate tokens, make sure those scopes are enabled for the Auth0 Management API.

To set the template, you need to use the following endpoint:


curl --request PUT \
  --url 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN' \
  --header 'content-type: text/html' \
  --data '<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login");
var request = new RestRequest(Method.PUT);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
request.AddHeader("content-type", "text/html");
request.AddParameter("text/html", "<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"

	payload := strings.NewReader("<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>")

	req, _ := http.NewRequest("PUT", url, payload)

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
	req.Header.Add("content-type", "text/html")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.put("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .header("content-type", "text/html")
  .body("<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'PUT',
  url: 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login',
  headers: {authorization: 'Bearer MGMT_API_ACCESS_TOKEN', 'content-type': 'text/html'},
  data: '<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>'
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN",
                           @"content-type": @"text/html" };

NSData *postData = [[NSData alloc] initWithData:[@"<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>" dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"PUT"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PUT",
  CURLOPT_POSTFIELDS => "<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer MGMT_API_ACCESS_TOKEN",
    "content-type: text/html"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>"

headers = {
    'authorization': "Bearer MGMT_API_ACCESS_TOKEN",
    'content-type': "text/html"
    }

conn.request("PUT", "/YOUR_DOMAIN/api/v2/branding/templates/universal-login", payload, headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Put.new(url)
request["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'
request["content-type"] = 'text/html'
request.body = "<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>"

response = http.request(request)
puts response.read_body
import Foundation

let headers = [
  "authorization": "Bearer MGMT_API_ACCESS_TOKEN",
  "content-type": "text/html"
]

let postData = NSData(data: "<!DOCTYPE html><html><head>{%- auth0:head -%}</head><body>{%- auth0:widget -%}</body></html>".data(using: String.Encoding.utf8)!)

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PUT"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

To retrieve the template, you need to use the following endpoint:


curl --request GET \
  --url 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.get("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'GET',
  url: 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login',
  headers: {authorization: 'Bearer MGMT_API_ACCESS_TOKEN'}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("GET", "/YOUR_DOMAIN/api/v2/branding/templates/universal-login", headers=headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

To delete the template, you need to use the following endpoint:


curl --request DELETE \
  --url 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login");
var request = new RestRequest(Method.DELETE);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"

	req, _ := http.NewRequest("DELETE", url, nil)

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.delete("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'DELETE',
  url: 'https://YOUR_DOMAIN/api/v2/branding/templates/universal-login',
  headers: {authorization: 'Bearer MGMT_API_ACCESS_TOKEN'}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/branding/templates/universal-login"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"DELETE"];
[request setAllHTTPHeaderFields:headers];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("DELETE", "/YOUR_DOMAIN/api/v2/branding/templates/universal-login", headers=headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Delete.new(url)
request["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/branding/templates/universal-login")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "DELETE"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

The maximum size for the Page Template is 100KB. If that is not big enough, consider moving images/css files outside of the Page Template code.

CSS customization

There are a few things that you can customize using CSS:

  • You can hide the tenant logo by adding class="_hide-prompt-logo" in the <body> element.

  • You can specify a custom logo by adding class="_use-custom-prompt-logo" in the <body> element. This would let you, for example, change the login page logo depending on the application:

<% if application.name === "website" %>
  #custom-prompt-logo {
    background-image: url('https://acme.com/website.png');
  }
<% elsif application.name === "employee_portal" %>
  #custom-prompt-logo {
    background-image: url('https://acme.com/acme.png');
  }
<% endif %>

The current implementation does not support further CSS customization. If you look at the HTML that is generated, you will see code like:

.c10d15918.c7b3b8672 {
  background: #D00E17;
}

The CSS class names change each time we build the project. If you write CSS that targets those classes, it will break.

The HTML structure of the pages might also change. If your customizations depend on it, they could stop working at any time.

Using the Auth0 CLI

You can use the Auth0 CLI to easily update Page Templates.

In the Auth0 CLI, run:

auth0 branding templates update

The Auth0 CLI will open two windows:

  • A browser window with a Storybook that shows the login page with the page template applied:

Page Templates Storybook
  • The default editor, with the page template code:

undefined

You can now change the page template code, and you will be able to preview the changes in your browser window.

Once you close the window, you’ll be asked if you want to save the template. If you answer Yes, the template will be uploaded to your tenant.

Troubleshooting

If the template is not being applied, check that you are navigating to <custom_domain>/authorize. When you navigate to YOUR_DOMAIN/authorize Auth0 will not render the page template.