diff --git a/app/auth/models.py b/app/auth/models.py
index 1022e90b89e98c2bde45db0097aa6ab74dcef65b..e6bebcb85a2a6740b8e224d31d483a4da30f1e04 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -38,6 +38,7 @@ from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
 import app.scodoc.sco_utils as scu
 
 VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
+DEFAULT_RESET_TOKEN_DURATION = 24 * 60 * 60  # seconds (default 24h)
 
 
 def is_valid_password(cleartxt) -> bool:
@@ -178,6 +179,43 @@ class User(UserMixin, ScoDocModel):
             raise ValueError("invalid user_id")
         return query.first_or_404() if not accept_none else query.first()
 
+    def can_login_using_scodoc(self) -> bool:
+        """True si l'utilisateur peut (essayer de) se connecter avec son compte local ScoDoc
+        (si par ailleurs un mot de passe valide existe et que le compte est actif)
+
+        Toujours vrai pour le super-admin.
+        Si CAS activé and cas_id renseigné, il faut cas_allow_scodoc_login.
+
+        Réglages possibles:
+        - Global : cas_force CAS forcé pour tous sauf super-admin
+        - Par utilisateur:
+            - cas_allow_login : Peut-on se logguer via le CAS ?
+            - cas_allow_scodoc_login : Si CAS activé, peut-on se logguer sur ScoDoc ?
+
+        """
+        if self.is_administrator():
+            return True  # super admin ou autorisation individuelle
+        cas_enabled = ScoDocSiteConfig.is_cas_enabled()
+        if not cas_enabled:
+            return True  # CAS not enabled
+
+        if not self.cas_allow_scodoc_login:
+            log(
+                f"""auth: {self.user_name
+                }: cas enabled, scodoc login not allowed"""
+            )
+            return False
+
+        if ScoDocSiteConfig.is_cas_forced() and self.cas_id and self.cas_allow_login:
+            log(
+                f"""auth: {self.user_name
+                } (cas_id='{
+                    self.cas_id}'): cas forced and cas_id set: scodoc login not allowed"""
+            )
+            return False
+
+        return True
+
     def set_password(self, password: str):
         "Set password"
         log(f"set_password({self})")
@@ -197,6 +235,7 @@ class User(UserMixin, ScoDocModel):
     def check_password(self, password: str) -> bool:
         """Check given password vs current one.
         Returns `True` if the password matched, `False` otherwise.
+        Also checks for temporary passwords and if CAS disables scodoc login.
         """
         if not self.active:  # inactived users can't login
             current_app.logger.warning(
@@ -214,28 +253,8 @@ class User(UserMixin, ScoDocModel):
             send_notif_desactivation_user(self)
             return False
 
-        # if CAS activated and cas_id, allow only super-user and users with cas_allow_scodoc_login
-        cas_enabled = ScoDocSiteConfig.is_cas_enabled()
-        if cas_enabled and not self.is_administrator():
-            if not self.cas_allow_scodoc_login:
-                # CAS activé et compte non autorisé à se logguer sur ScoDoc
-                log(
-                    f"""auth: login attempt for user {self.user_name}: scodoc login not allowed
-                    """
-                )
-                return False
-            # si CAS activé et forcé et cas_id renseigné, on ne peut pas se logguer
-            if (
-                self.cas_id
-                and self.cas_allow_login
-                and ScoDocSiteConfig.get("cas_force")
-            ):
-                log(
-                    f"""auth: login attempt for user {self.user_name
-                        } (cas_id='{
-                        self.cas_id}'): cas forced and cas_id set: scodoc login not allowed"""
-                )
-                return False
+        if not self.can_login_using_scodoc():
+            return False
 
         if not self.password_hash:  # user without password can't login
             if self.password_scodoc7:
@@ -258,10 +277,16 @@ class User(UserMixin, ScoDocModel):
             return True
         return False
 
-    def get_reset_password_token(self, expires_in=24 * 60 * 60):
+    def get_reset_password_token(
+        self, expires_in=DEFAULT_RESET_TOKEN_DURATION
+    ) -> str | None:
         """Un token pour réinitialiser son mot de passe.
         Par défaut valide durant 24 heures.
+        Note: si le CAS est obligatoire pour l'utilisateur, renvoie None
         """
+        # si la config CAS interdit le login ScoDoc, pas de token
+        if not self.can_login_using_scodoc():
+            return None
         token = jwt.encode(
             {"reset_password": self.id, "exp": time() + expires_in},
             current_app.config["SECRET_KEY"],
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 6adb9a1e8c58b10f05a5b9d51e1dd630171c05a8..51c2a939cbaced1b409a188785670e6d342bc0bb 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -87,7 +87,7 @@ def login():
     if current_user.is_authenticated:
         return redirect(url_for("scodoc.index"))
 
-    if ScoDocSiteConfig.get("cas_force"):
+    if ScoDocSiteConfig.is_cas_forced():
         current_app.logger.info("login: forcing CAS")
         return redirect(url_for("cas.login"))
 
diff --git a/app/templates/email/reset_password.j2 b/app/templates/email/reset_password.j2
index 9e92a785e1d0c9b19480492aefbc290c89869b2f..44fcfbf33ab65ef86b5f579d6d50756eea06104b 100644
--- a/app/templates/email/reset_password.j2
+++ b/app/templates/email/reset_password.j2
@@ -1,4 +1,7 @@
+
 <p>Bonjour {{ user.user_name }},</p>
+
+{% if token %}
 <p>
     Pour réinitialiser votre mot de passe ScoDoc,
     <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
@@ -8,6 +11,14 @@
 <p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
 <p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
 
+{% else %}
+<p>Vous ne pouvez pas changer votre mot de passe sur ScoDoc:
+en effet, pour vous connecter, vous devez utiliser votre identifiant universitaire
+sur le système d'authentification de votre établissement (CAS, ENT).
+</p>
+{% endif %}
+
+
 <p>Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
     ScoDoc, vous pouvez simplement ignorer ce message.
 </p>
diff --git a/app/templates/email/reset_password.txt b/app/templates/email/reset_password.txt
index 2136a52d61908486be034c1c1e395c24d4a79527..e308c54ea1815c3a089f3645cfcbc6a3124d68a3 100644
--- a/app/templates/email/reset_password.txt
+++ b/app/templates/email/reset_password.txt
@@ -1,10 +1,19 @@
 Bonjour {{ user.user_name }},
 
+{% if token %}
+
 Pour réinitialiser votre mot de passe ScoDoc, suivre le lien:
 
 {{ url_for('auth.reset_password', token=token, _external=True) }}
 
 
+{% else %}
+Vous ne pouvez pas changer votre mot de passe sur ScoDoc:
+en effet, pour vous connecter, vous devez utiliser votre identifiant universitaire
+sur le système d'authentification de votre établissement (CAS, ENT).
+
+{% endif %}
+
 Si vous n'avez pas demandé à réinitialiser votre mot de passe sur
 ScoDoc, vous pouvez simplement ignorer ce message.
 
diff --git a/app/templates/email/welcome.j2 b/app/templates/email/welcome.j2
index a785479c26cc951342b71e6ff2aaf73e7c9bfcbd..01d045b3e431069d2a806b13b7210fddc0358f59 100644
--- a/app/templates/email/welcome.j2
+++ b/app/templates/email/welcome.j2
@@ -5,20 +5,21 @@
 <p>
     Votre identifiant ScoDoc est: <b>{{ user.user_name }}</b>
 </p>
-{% if cas_force %}
-<p>
-    Pour vous connecter, vous devrez utiliser votre identifiant universitaire
-    sur le système d'authentification de votre établissement (CAS, ENT).
-</p>
-{% endif %}
+
 {% if token %}
-<p>Pour initialiser votre mot de passe ScoDoc,
-    <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
-        cliquez sur ce lien
-    </a>.
-</p>
-<p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
-<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
+    <p>Pour initialiser votre mot de passe ScoDoc,
+        <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">
+            cliquez sur ce lien
+        </a>.
+    </p>
+    <p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
+    <p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
+
+    <p>Ce lien expirera le {{date_expiration_token}}</p>
+{% else %}
+    <p>Pour vous connecter, vous devrez utiliser votre identifiant universitaire
+    sur le système d'authentification de votre établissement (CAS, ENT).
+    </p>
 {% endif %}
 
 <p>A bientôt !</p>
diff --git a/app/templates/email/welcome.txt b/app/templates/email/welcome.txt
index dffb870b54c30e1f5da72d5719a962daddea4336..99072d373f9b320a4276849ef6075a9a8764ac6f 100644
--- a/app/templates/email/welcome.txt
+++ b/app/templates/email/welcome.txt
@@ -3,16 +3,16 @@ Bienvenue {{ user.prenom }} {{ user.nom }},
 Votre accès à ScoDoc vient d'être validé.
 Votre identifiant ScoDoc est: {{ user.user_name }}
 
-{% if cas_force %}
-<p>
-    Pour vous connecter, vous devrez utiliser votre identifiant universitaire
-    sur le système d'authentification de votre établissement (CAS, ENT).
-</p>
-{% endif %}
-
 {% if token %}
-    Pour initialiser votre mot de passe ScoDoc, suivre le lien:
-    {{ url_for('auth.reset_password', token=token, _external=True) }}
+
+Pour initialiser votre mot de passe ScoDoc, suivre le lien:
+{{ url_for('auth.reset_password', token=token, _external=True) }}
+
+Ce lien expirera le {{date_expiration_token}}
+{% else %}
+
+Pour vous connecter, vous devrez utiliser votre identifiant universitaire
+sur le système d'authentification de votre établissement (CAS, ENT).
 {% endif %}
 
 A bientôt !
diff --git a/app/views/users.py b/app/views/users.py
index dd26a312895cd11b9999cb8f9fefe33952f44d5e..8612d0a8f94032d53ad224b7b8c426d6e273dd61 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -35,6 +35,7 @@ Emmanuel Viennet, 2021
 """
 import datetime
 import re
+import time
 from enum import auto, IntEnum
 from xml.etree import ElementTree
 
@@ -50,11 +51,14 @@ from wtforms.validators import DataRequired, Email, ValidationError, EqualTo
 from app import db
 from app import email
 from app.auth.forms import DeactivateUserForm
-from app.auth.models import Permission
-from app.auth.models import User
-from app.auth.models import Role
-from app.auth.models import UserRole
-from app.auth.models import is_valid_password
+from app.auth.models import (
+    DEFAULT_RESET_TOKEN_DURATION,
+    Permission,
+    User,
+    Role,
+    UserRole,
+    is_valid_password,
+)
 from app.models import Departement
 from app.models.config import ScoDocSiteConfig
 
@@ -64,10 +68,13 @@ from app.decorators import (
     permission_required,
 )
 
-from app.scodoc import sco_import_users, sco_roles_default
-from app.scodoc import sco_users
-from app.scodoc import sco_utils as scu
-from app.scodoc import sco_xml
+from app.scodoc import (
+    sco_import_users,
+    sco_roles_default,
+    sco_users,
+    sco_utils as scu,
+    sco_xml,
+)
 from app import log
 from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied, ScoValueError
 from app.scodoc.sco_import_users import generate_password
@@ -779,11 +786,9 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
         # B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
         # C: Aucun envoi (mot de passe saisi dans le formulaire)
         if vals["welcome"]:  # "Envoie un mail d'accueil" coché
-            if vals["reset_password"] and (
-                (not ScoDocSiteConfig.get("cas_force"))
-                or vals.get("cas_allow_scodoc_login", False)
-            ):
-                # nb: si login scodoc non autorisé car CAS seul, n'envoie pas le mot de passe.
+            if vals["reset_password"]:
+                # nb: le token ne sera envoyé que si le login ScoDoc est autorisé,
+                # voir get_reset_password_token()
                 mode = Mode.WELCOME_AND_CHANGE_PASSWORD
             else:
                 mode = Mode.WELCOME_ONLY
@@ -828,13 +833,10 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
         db.session.commit()
         # envoi éventuel d'un message
         if mode in (Mode.WELCOME_AND_CHANGE_PASSWORD, Mode.WELCOME_ONLY):
-            token = (
-                the_user.get_reset_password_token()
-                if mode == Mode.WELCOME_AND_CHANGE_PASSWORD
-                else None
-            )
-
-            cas_force = ScoDocSiteConfig.get("cas_force")
+            token = the_user.get_reset_password_token()
+            date_expiration_token = datetime.datetime.fromtimestamp(
+                time.time() + DEFAULT_RESET_TOKEN_DURATION
+            ).strftime(scu.DATEATIME_FMT)
             # Le from doit utiliser la préférence du département de l'utilisateur
             email.send_email(
                 "[ScoDoc] Création de votre compte",
@@ -844,13 +846,13 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
                     "email/welcome.txt",
                     user=the_user,
                     token=token,
-                    cas_force=cas_force,
+                    date_expiration_token=date_expiration_token,
                 ),
                 html_body=render_template(
                     "email/welcome.j2",
                     user=the_user,
                     token=token,
-                    cas_force=cas_force,
+                    date_expiration_token=date_expiration_token,
                 ),
             )
             flash(f"Mail accueil envoyé à {the_user.email}")