AuthLogin - Mire d’authentification Keycloak
Cette page est la spécification design de la mire de connexion
Keycloak (login.ftl). Elle est destinée aux équipes qui développent
le thème Keycloak à partir de cette référence.
Rappel décision K.1 : pas de composant React/Angular pour cet écran. Le rendu cible est du HTML+CSS pur rendu côté serveur Keycloak (FTL). Les blocs ci-dessous sont à copier-coller dans le
.ftlavec les adaptations FreeMarker indiquées.
Contexte d’usage
L’écran s’affiche quand un usager polynésien tente d’accéder à une app
administrative et n’est pas authentifié. Keycloak redirige vers
login.ftl, qui doit produire le rendu ci-dessous.
Rendu visuel - Variante par défaut
Variante - Avec message d’erreur
Connexion
Pas encore de compte ? Créer un compte
Code HTML pour le .ftl Keycloak
À copier-coller dans login.ftl. Les blocs FreeMarker (<#if …>,
${…}) sont expliqués dans la section spec ci-après.
<div class="pf-auth-page">
<div class="ori-card ori-card--elevated pf-auth-card">
<div class="ori-card__body pf-auth-body">
<div class="pf-auth-brand">
<span class="ori-logo">
<img src="${url.resourcesPath}/img/logo-pf.svg" alt="" class="ori-logo__crest" />
<span class="ori-logo__text">
<span class="ori-logo__title">Polynésie française</span>
<span class="ori-logo__subtitle">${realm.displayName!''}</span>
</span>
</span>
</div>
<h1 class="pf-auth-title">${msg("loginAccountTitle")}</h1>
<#if message?has_content && (messagesPerField.exists('username') || messagesPerField.exists('password'))>
<div class="ori-alert ori-alert--danger" role="alert">
<div class="ori-alert__content">${kcSanitize(message.summary)?no_esc}</div>
</div>
</#if>
<form id="kc-form-login" action="${url.loginAction}" method="post" class="pf-auth-form">
<div class="ori-field">
<label for="username" class="ori-field__label ori-field__label--required">${msg("usernameOrEmail")}</label>
<input id="username" name="username" type="text" autocomplete="username"
value="${(login.username!'')}" required class="ori-input"
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>" />
</div>
<div class="pf-auth-password-block">
<div class="ori-field">
<label for="password" class="ori-field__label ori-field__label--required">${msg("password")}</label>
<input id="password" name="password" type="password" autocomplete="current-password"
required class="ori-input"
aria-invalid="<#if messagesPerField.existsError('password')>true</#if>" />
</div>
<#if realm.resetPasswordAllowed>
<a href="${url.loginResetCredentialsUrl}" class="ori-link ori-link--quiet pf-auth-forgot">${msg("doForgotPassword")}</a>
</#if>
</div>
<button type="submit" class="ori-btn ori-btn--primary ori-btn--block">${msg("doLogIn")}</button>
</form>
<#if realm.registrationAllowed && !registrationDisabled??>
<p class="pf-auth-register">
${msg("noAccount")} <a href="${url.registrationUrl}" class="ori-link">${msg("doRegister")}</a>
</p>
</#if>
</div>
</div>
</div>
CSS d’accompagnement (à ajouter au theme Keycloak)
Les classes pf-auth-* ne font pas partie du DS - ce sont des classes
de page propres au template Keycloak. Elles servent uniquement à
positionner le bloc.
.pf-auth-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
background-color: var(--color-surface-muted);
}
.pf-auth-card { width: 100%; max-width: 24rem; }
.pf-auth-body { display: flex; flex-direction: column; gap: 1.25rem; }
.pf-auth-brand { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
.pf-auth-title { margin: 0; font-size: 1.125rem; font-weight: 600; text-align: center; }
.pf-auth-form { display: flex; flex-direction: column; gap: 0.875rem; }
.pf-auth-password-block { display: flex; flex-direction: column; gap: 0.25rem; }
.pf-auth-forgot { align-self: flex-end; font-size: 0.8125rem; }
.pf-auth-register { margin: 0; text-align: center; font-size: 0.875rem; color: var(--color-text-secondary); }
Spécification fonctionnelle
Libellés (bundle messages_fr.properties)
loginAccountTitle=Connexion
usernameOrEmail=Identifiant
password=Mot de passe
doLogIn=Se connecter
doForgotPassword=Mot de passe oublié ?
noAccount=Pas encore de compte ?
doRegister=Créer un compte
Champs HTTP postés à ${url.loginAction}
| Champ | Type | Notes |
|---|---|---|
username | text ou email | Selon le realm. Autocomplete username. |
password | password | Toujours autocomplete="current-password". |
Conditionnels FTL utilisés
realm.resetPasswordAllowed- affiche le lien “Mot de passe oublié ?”realm.registrationAllowed && !registrationDisabled??- affiche le bloc de création de comptemessagesPerField.existsError('username')/('password')- appliquearia-invalid="true"sur le champ correspondantmessage?has_content- affiche l’alerte d’erreur globale en haut
Accessibilité
<form>natif : soumission par Enter<label>lié à chaque<input>viafor=aria-invalid="true"sur le champ en erreur<div class="ori-alert" role="alert">annoncé immédiatement par les lecteurs d’écran- Focus initial : sur le champ
username(Keycloak le gère par défaut, pas d’override nécessaire)
Anti-patterns à ne pas reproduire
- ❌ Pas de captcha visuel imposé à la première tentative (anti-pattern RGAA)
- ❌ Pas de “voir le mot de passe” non sécurisé (si demandé, à spécifier ultérieurement avec mécanisme de masquage temporel)
- ❌ Pas de boutons “Connexion via Google / FranceConnect” sur cette page (à spécifier dans un écran séparé)
Variantes selon la config du realm
| Realm config | Effet visuel |
|---|---|
realm.resetPasswordAllowed = false | Le lien “Mot de passe oublié ?” disparaît |
realm.registrationAllowed = false | Le bloc “Pas encore de compte ?” disparaît entièrement |
| Identifiant = email uniquement | <input type="email"> au lieu de text, libellé “Adresse email” via override de usernameOrEmail |
Champ identifiant pré-rempli (loginHint) | Focus initial sur password au lieu de username |