Upgrade Notes
Overview
New versions of FusionAuth sometimes include new or updated theme templates. If a new template is not part of a custom theme, FusionAuth will render the template from the default theme. Occasionally, modifications to existing templates or helper macros introduced in a FusionAuth release will require changes to an existing custom theme in order for it to continue functioning correctly.
This page contains notes on changes in recent versions of FusionAuth that require changes to a customized theme.
Version 1.59.0
Starting in version 1.59.0
, user passwords are optional and phone identities are now supported. While theme updates are not required, there are some areas that you may want to consider making changes.
Template | Changed For Phone Identities | Password |
---|---|---|
Messages | X | |
Account edit template (self-service) | X | |
Account index template (self-service) | X | |
Forgot password template | X | |
Forgot password sent template | X | |
OAuth complete registration template | X | |
OAuth passwordless template | X | |
OAuth register template | X |
Messages
To properly support phone identities, several themed Messages
have changed. If forgot-password-email-sent
or forgot-password-email-sent-title
was previously customized in a theme, the same customizations need to be applied to the new forgot-password-message-sent
and forgot-password-message-sent-title
messages.
Messages Before
forgot-password-email-sent=We have sent an email to %s containing a link that will allow you to reset your password. Once you receive the email follow the instructions to change your password.
forgot-password-email-sent-title=Email sent
loginId=Email
...
[PasswordlessRequestSent]=An email is on the way.
Messages After
forgot-password-message-sent=We have sent a message to %s containing a link that will allow you to reset your password. Once you receive the message follow the instructions to change your password.
forgot-password-message-sent-title=Message sent
loginId=Login
...
[PasswordlessRequestSent]=A message is on the way.
Account edit template (self-service)
To accommodate the use case of an existing user, without a password, setting a password for the first time, there is a new template variable, passwordSet
, that can be used on the Helpers
page to hide the “current password” field for users that do not have a password.
Before
<form action="${request.contextPath}/account/edit" method="POST" class="full" id="user-form">
...
[#list fieldValues as field]
[#if field.key == "user.password"]
[@helpers.passwordField field application.formConfiguration.selfServiceFormConfiguration.requireCurrentPasswordOnPasswordChange/]
[#else]
[@helpers.customField field=field key=field.key autofocus=false placeholder=field.key label=theme.optionalMessage(field.key) leftAddon="false"/]
[#if field.confirm]
[@helpers.customField field "confirm.${field.key}" false "[confirm]${field.key}" /]
[/#if]
[/#if]
[/#list]
</form>
After
<form action="${request.contextPath}/account/edit" method="POST" class="full" id="user-form">
...
[#list fieldValues as field]
[#if field.key == "user.password"]
[@helpers.passwordField field=field
showCurrentPasswordField=(passwordSet && application.formConfiguration.selfServiceFormConfiguration.requireCurrentPasswordOnPasswordChange)/]
[#else]
[@helpers.customField field=field key=field.key autofocus=false placeholder=field.key label=theme.optionalMessage(field.key) leftAddon="false"/]
[#if field.confirm]
[@helpers.customField field "confirm.${field.key}" false "[confirm]${field.key}" /]
[/#if]
[/#if]
[/#list]
</form>
Account index template (self-service)
New installations of FusionAuth show the new user.phoneNumber
field on the account index page, rather than user.mobilePhone
.
Before
<dl class="horizontal">
<dt>${theme.message("user.mobilePhone")}</dt>
<dd>${helpers.display(user, "mobilePhone")}</dd>
</dl>
After
<dl class="horizontal">
<dt>${theme.message("user.phoneNumber")}</dt>
<dd>${fusionAuth.phone_format(user.phoneNumber!"\x2013")}</dd>
</dl>
Forgot password template
To improve the user experience for phone number identities, the email
field should be replaced with a loginId
field which will use the label Login
instead of Email
(see Messages above). Everything will continue to function even if this change is not made, but the user experience will be improved with the new field because of the more accurate label.
Before
<fieldset class="push-less-top">
[@helpers.input type="text" name="email" id="email" autocapitalize="none" autofocus=true autocomplete="on" autocorrect="off" placeholder=theme.message('email') leftAddon="user" required=true/]
[@helpers.captchaBadge showCaptcha=showCaptcha captchaMethod=tenant.captchaConfiguration.captchaMethod siteKey=tenant.captchaConfiguration.siteKey/]
</fieldset>
After
<fieldset class="push-less-top">
[@helpers.input type="text" name="loginId" id="loginId" autocapitalize="none" autofocus=true autocomplete="on" autocorrect="off" placeholder=theme.message('loginId') leftAddon="user" required=true/]
[@helpers.captchaBadge showCaptcha=showCaptcha captchaMethod=tenant.captchaConfiguration.captchaMethod siteKey=tenant.captchaConfiguration.siteKey/]
</fieldset>
Forgot password sent template
Similar to the Forgot password
template, the Forgot password sent
template should also be updated to use the loginId
field instead of the email
field. This will improve the user experience for phone number identities.
Before
[@helpers.main title=theme.message('forgot-password-email-sent-title')]
<p>
${theme.message('forgot-password-email-sent', email)}
</p>
<p class="mt-2">[@helpers.link url="/oauth2/authorize"]${theme.message('return-to-login')}[/@helpers.link]</p>
[/@helpers.main]
After
[@helpers.main title=theme.message('forgot-password-message-sent-title')]
<p>
${theme.message('forgot-password-message-sent', loginId)}
</p>
<p class="mt-2">[@helpers.link url="/oauth2/authorize"]${theme.message('return-to-login')}[/@helpers.link]</p>
[/@helpers.main]
OAuth complete registration template
You may need users to have passwords, but passwords are optional. Consider the following example:
- A user has already logged in
- The user does not have a password
- The user performs self-serve registration for an application that uses basic registration
In this case, FusionAuth takes the user to the OAuth complete registration
page to add any required fields for the application. If the user does not have a password you can collect it on this page.
To support this, FusionAuth provides a new template variable named passwordSet
, which indicates whether or not the user has a password. Use this variable to show a password field if you require the user to have a password.
Note that for a new or logged-out user that registers for this application, the password will generally be collected on the initial registration page. Logged-in users bypass the initial page.
For example, before:
Before
[#if application.registrationConfiguration.mobilePhone.enabled]
[@helpers.input type="text" name="user.mobilePhone" id="mobilePhone" placeholder=theme.message("mobilePhone") leftAddon="phone" required=application.registrationConfiguration.mobilePhone.required/]
[/#if]
[#if application.registrationConfiguration.preferredLanguages.enabled]
[@helpers.locale_select field="" name="user.preferredLanguages" id="preferredLanguages" label=theme.message("preferredLanguage") required=application.registrationConfiguration.preferredLanguages.required /]
[/#if]
After
[#if application.registrationConfiguration.mobilePhone.enabled]
[@helpers.input type="text" name="user.mobilePhone" id="mobilePhone" placeholder=theme.message("mobilePhone") leftAddon="phone" required=application.registrationConfiguration.mobilePhone.required/]
[/#if]
[#if !(passwordSet!false)]
[@helpers.input type="password" name="user.password" id="password" autocomplete="new-password" placeholder=theme.message('password') leftAddon="lock" required=true/]
[#if application.registrationConfiguration.confirmPassword]
[@helpers.input type="password" name="confirm.user.password" id="passwordConfirm" autocomplete="new-password" placeholder=theme.message('passwordConfirm') leftAddon="lock" required=true/]
[/#if]
[/#if]
[#if application.registrationConfiguration.preferredLanguages.enabled]
[@helpers.locale_select field="" name="user.preferredLanguages" id="preferredLanguages" label=theme.message("preferredLanguage") required=application.registrationConfiguration.preferredLanguages.required /]
[/#if]
OAuth passwordless template
To support passwordless logins with phone numbers, several changes must be made to allow submission of one-time or short codes on the passwordless page. The changes include: new hidden form fields, an if
surrounding the loginId
field with a new oneTimeCode
field, and a change to the button text based on whether the form is being submitted or a code is being sent.
Before
<form action="${request.contextPath}/oauth2/passwordless" method="POST" class="full">
[@helpers.oauthHiddenFields/]
<fieldset>
[@helpers.input type="text" name="loginId" id="loginId" autocomplete="username" autocapitalize="none" autocomplete="on" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message("loginId") leftAddon="user" required=true/]
[@helpers.captchaBadge showCaptcha=showCaptcha captchaMethod=tenant.captchaConfiguration.captchaMethod siteKey=tenant.captchaConfiguration.siteKey/]
</fieldset>
[@helpers.input id="rememberDevice" type="checkbox" name="rememberDevice" label=theme.message("remember-device") value="true" uncheckedValue="false"]
<i class="fa fa-info-circle" data-tooltip="${theme.message('{tooltip}remember-device')}"></i>[#t/]
[/@helpers.input]
<div class="form-row">
[@helpers.button icon="send" text=theme.message('send')/]
<p class="mt-2">[@helpers.link url="/oauth2/authorize"]${theme.message('return-to-login')}[/@helpers.link]</p>
</div>
</form>
After
<form action="${request.contextPath}/oauth2/passwordless" method="POST" class="full">
[@helpers.oauthHiddenFields/]
[@helpers.hidden name="code"/]
[@helpers.hidden name="formField"/]
<fieldset>
[#if (formField!false)]
[@helpers.input type="text" name="oneTimeCode" id="otp" autocapitalize="none" autofocus=true autocomplete="one-time-code" autocorrect="off" placeholder="${theme.message('passwordless-code')}" leftAddon="lock"/]
[#else]
[@helpers.input type="text" name="loginId" id="loginId" autocomplete="username" autocapitalize="none" autocomplete="on" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message("loginId") leftAddon="user" required=true/]
[/#if]
[@helpers.captchaBadge showCaptcha=showCaptcha captchaMethod=tenant.captchaConfiguration.captchaMethod siteKey=tenant.captchaConfiguration.siteKey/]
</fieldset>
[@helpers.input id="rememberDevice" type="checkbox" name="rememberDevice" label=theme.message("remember-device") value="true" uncheckedValue="false"]
<i class="fa fa-info-circle" data-tooltip="${theme.message('{tooltip}remember-device')}"></i>[#t/]
[/@helpers.input]
<div class="form-row">
[#if (formField!false)]
[@helpers.button text=theme.message('submit')/]
[#else]
[@helpers.button icon="send" text=theme.message('send')/]
[/#if]
</div>
</form>
OAuth register template
To support phone numbers in basic registration, a user.phoneNumber
field must be added.
Before
[#if application.registrationConfiguration.loginIdType == 'email']
[@helpers.input type="text" name="user.email" id="email" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message('email') leftAddon="user" required=true/]
[#else]
[@helpers.input type="text" name="user.username" id="username" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message('username') leftAddon="user" required=true/]
[/#if]
After
[#if application.registrationConfiguration.loginIdType == 'email']
[@helpers.input type="text" name="user.email" id="email" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message('email') leftAddon="user" required=true/]
[#elseif application.registrationConfiguration.loginIdType == 'phoneNumber']
[@helpers.input type="text" name="user.phoneNumber" id="phoneNumber" autocomplete="mobile" autocapitalize="none" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message('phoneNumber') leftAddon="mobile" required=true/]
[#else]
[@helpers.input type="text" name="user.username" id="username" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" autofocus=true placeholder=theme.message('username') leftAddon="user" required=true/]
[/#if]
Version 1.53.3
Version 1.53.3
includes a change to persist the value of the Keep me signed in
checkbox from the hosted login pages through an external identity provider workflow. This checkbox value indicates whether the user wishes to create an SSO session after login. If the Google IdP’s loginMethod
is configured as UsePopup
or UseVendorJavaScript
, existing custom advanced themes require an update to incorporate the fix for the Google IdP. You can update the template via the API using theme.templates.helpers
or by modifying the Helpers template in the admin UI. Google IdPs configured with a loginMethod
value of UseRedirect
do not require this update, but you may consider making the change preemptively in case the loginMethod
is changed later.
To allow the Keep me signed in
value to be persisted through a Google IdP login in an existing custom advanced theme, remove the data-login_uri
attribute and its value from the div
with Id g_id_onload
in the googleButton
macro and add the data-callback
attribute in its place.
Replace
<div id="g_id_onload" [#list identityProvider.lookupAPIProperties(clientId)!{} as attribute, value] data-${attribute}="${value}" [/#list]
data-client_id="${identityProvider.lookupClientId(clientId)}"
data-login_uri="${currentBaseURL}/oauth2/callback?state=${idpRedirectState}&identityProviderId=${identityProvider.id}" >
</div>
with
<div id="g_id_onload" [#list identityProvider.lookupAPIProperties(clientId)!{} as attribute, value] data-${attribute}="${value}" [/#list]
data-client_id="${identityProvider.lookupClientId(clientId)}"
data-callback="googleLoginCallback" >
</div>
Version 1.52.0
Version 1.52.0
includes a change to use the browser-default date picker to enhance the experience on mobile. Existing custom advanced themes require an update to incorporate the change. You can update the template via the API using theme.templates.helpers
or by modifying the Helpers template in the admin UI.
To include the new date picker in an existing custom advanced theme, replace the Prime.Document.query('.date-picker')
line in the head
macro with the following:
document.querySelectorAll('.date-picker').forEach(datePicker => {
datePicker.onfocus = () => datePicker.type = 'date';
datePicker.onblur = () => {
if (datePicker.value === '') {
datePicker.type = 'text';
}
};
});
Version 1.50.0
Version 1.50.0
added the ability to prompt users for consent to custom OAuth scopes in third-party applications. This change requires a new themed template oauth2Consent
as well as a new macro and function in the helpers
template.
The oauth2Consent
template from the default theme will be used until it is added to an existing custom theme. You can copy the new template from the default theme as a starting point and add it to a custom theme via the API using theme.templates.oauth2Consent
or the Consent prompt template in the admin UI.
The new scopeConsentField
macro and resolveScopeMessaging
function must be added to an existing custom theme’s helpers
template in order for the theme to continue functioning. Add these new items to the template via the API using theme.templates.helpers
or the Helpers template in the admin UI. You can copy them from the default template or use the following:
[#macro scopeConsentField application scope type]
[#-- Resolve the consent message and detail for the provided scope --]
[#if type != "unknown"]
[#local scopeMessage = resolveScopeMessaging('message', application, scope.name, scope.defaultConsentMessage!scope.name) /]
[#local scopeDetail = resolveScopeMessaging('detail', application, scope.name, scope.defaultConsentDetail!'') /]
[/#if]
[#if type == "required"]
[#-- Required scopes should use a hidden form field with a value of "true". The user cannot change this selection, --]
[#-- but there should be a display element to inform the user that they must consent to the scopes to continue. --]
<div class="form-row consent-item col-lg-offset-0">
[@hidden name="scopeConsents['${scope.name}']" value="true" /]
<i class="fa fa-check"></i>
<span>
${scopeMessage}
[#if scopeDetail?has_content]
<i class="fa fa-info-circle" data-tooltip="${scopeDetail}"></i>
[/#if]
</span>
</div>
[#elseif type == "optional"]
[#-- Optional scopes should render a checkbox to allow a user to change their selection. The available values should be "true" and "false" --]
<div class="consent-item col-lg-offset-0">
[@input type="checkbox" name="scopeConsents['${scope.name}']" id="${scope.name}" label=scopeMessage value="true" uncheckedValue="false" tooltip=scopeDetail /]
</div>
[#elseif type == "unknown"]
[#-- Unknown scopes and the reserved "openid" and "offline_access" scopes are considered required and do not have an associated display element. --]
[@hidden name="scopeConsents['${scope}']" value="true" /]
[/#if]
[/#macro]
[#function resolveScopeMessaging messageType application scopeName default]
[#-- Application specific, tenant specific, not application/tenant specific, then default --]
[#local message = theme.optionalMessage("[{application}${application.id}]{scope-${messageType}}${scopeName}") /]
[#local resolvedMessage = message != "[{application}${application.id}]{scope-${messageType}}${scopeName}" /]
[#if !resolvedMessage]
[#local message = theme.optionalMessage("[{tenant}${application.tenantId}]{scope-${messageType}}${scopeName}") /]
[#local resolvedMessage = message != "[{tenant}${application.tenantId}]{scope-${messageType}}${scopeName}" /]
[/#if]
[#if !resolvedMessage]
[#local message = theme.optionalMessage("{scope-${messageType}}${scopeName}") /]
[#local resolvedMessage = message != "{scope-${messageType}}${scopeName}" /]
[/#if]
[#if !resolvedMessage]
[#return default /]
[#else]
[#return message /]
[/#if]
[/#function]