Aller au contenu

Accès granulaire avec target

Le paramètre target permet d’ouvrir un cours, un exercice ou une autre page précise après l’authentification via une landing ProxyNinja. La ressource doit lire ce paramètre et réaliser la navigation finale.

Privilégiez un chemin relatif à la racine de votre ressource, comme /cours/42. Encodez la valeur complète une seule fois :

const target = '/cours/42?chapter=2#exercise';
const landing = new URL('https://landing.proxyninja.fr/votre-ressource-pronote');
landing.searchParams.set('target', target); // encode automatiquement la valeur
const link = landing.href;

Lien obtenu :

https://landing.proxyninja.fr/votre-ressource-pronote?target=%2Fcours%2F42%3Fchapter%3D2%23exercise

Remplacez votre-ressource-pronote par le slug communiqué pour votre intégration. Vous pouvez aussi concaténer '?target=' + encodeURIComponent(target), mais n’appliquez pas encodeURIComponent avant searchParams.set : cela encoderait deux fois la valeur. Les &, ? et # de la destination font partie de target, pas des paramètres de la landing.

ProxyNinja ajoute target à la redirectUri configurée pour le slug, puis conserve cette URI pendant le parcours d’authentification de la landing.

Exemple générique, avec le point d’entrée https://ressource.example/api/auth/sso :

https://ressource.example/api/auth/sso?target=%2Fcours%2F42%3Fchapter%3D2%23exercise

Votre parseur de query string restitue alors :

/cours/42?chapter=2#exercise
Cas Contrat de propagation
target absent ou vide La redirectUri reste inchangée ; conserver votre accueil habituel.
Chemin /cours/42 ou cours/42 Transmis tel quel ; la ressource choisit sa base de résolution.
URL absolue Transmise telle quelle ; la ressource valide son origine avant de l’utiliser.
Paramètres déjà présents dans la redirectUri Conservés ; un ancien paramètre target est remplacé.
Plusieurs paramètres target sur la landing La première valeur est utilisée ; ne générez qu’un seul paramètre.

target est un paramètre de navigation fourni par l’utilisateur. Il ne constitue ni un attribut d’identité, ni une preuve d’autorisation, ni une revendication de jeton. Le domaine et le chemin du point d’entrée configuré restent les mêmes. Ce contrat concerne la landing classique ; le lancement LTI utilise son propre target_link_uri.

Avec OpenAthens, la destination voyage dans target_link_uri. OpenAthens renvoie l’usager vers l’URL de connexion de l’application, c’est-à-dire la landing ProxyNinja, avec iss et target_link_uri. Sur les routes que ProxyNinja a configurées pour cela, la landing reprend target_link_uri comme target, et votre ressource la reçoit exactement comme ci-dessus.

  • Accepté : un chemin relatif commençant par un seul / (/cours/42), transmis tel quel ; ou une URL absolue https sur une origine déclarée pour la route.
  • Écarté : cours/42, //hote, /%2F%2Fhote, /\hote, toute autre origine. L’usager est alors connecté normalement et arrive sur votre accueil.

Pour une route non configurée, target_link_uri est ignoré : la connexion fonctionne, sans retour à la page précise. Les liens que vos usagers partagent entre collègues d’un même établissement sont décrits dans Liens partageables.

Lisez target dès l’arrivée sur votre point d’entrée SSO, avant toute redirection supplémentaire. Utilisez le parseur de votre framework (URLSearchParams, $request->query('target'), $_GET['target']…) et n’effectuez pas de second décodage manuel.

Résolvez les chemins relatifs contre une origine fixe de votre application, pas contre l’hôte de la landing ni un en-tête Host non validé. La politique conseillée est de n’accepter que vos propres pages de contenu. Rejetez les origines externes, les schémas autres que HTTPS, les URLs avec identifiants et les destinations vers vos endpoints de login/logout/callback.

Exemple JavaScript exécutable, à adapter à vos routes :

const RESOURCE_ORIGIN = 'https://ressource.example';
const DEFAULT_DESTINATION = RESOURCE_ORIGIN + '/';
function validateTarget(raw) {
if (raw == null || raw === '') return DEFAULT_DESTINATION;
if (typeof raw !== 'string' || raw.trim() !== raw) return null;
if (/[\u0000-\u0020\u007f\\]/.test(raw) || raw.startsWith('//')) return null;
let destination;
try {
destination = new URL(raw, RESOURCE_ORIGIN + '/');
} catch {
return null;
}
if (destination.origin !== RESOURCE_ORIGIN) return null;
if (destination.username || destination.password) return null;
// Exemple : seules les pages /cours/:id et /exercices/:id sont admises.
// Adaptez cette liste à vos contenus ; excluez les routes d'authentification.
if (!/^\/(cours|exercices)\/[A-Za-z0-9_-]+$/.test(destination.pathname)) return null;
return destination.href; // URL absolue normalisée sur notre origine
}
const incoming = new URL(requestUrl, RESOURCE_ORIGIN); // URL de la requête entrante
const values = incoming.searchParams.getAll('target');
const destination = values.length > 1 ? null : validateTarget(values[0]);
// null : répondre 400, ou choisir explicitement un accueil sûr.

Le filtre de routes est une première validation. Vos contrôleurs doivent aussi vérifier que l’utilisateur a le droit de consulter le contenu demandé. Ne faites pas de redirection directe avec la valeur brute de target : cela créerait une redirection ouverte (OAuth 2.0, section 10.15).

2. Conserver target pendant votre authentification

Section intitulée « 2. Conserver target pendant votre authentification »

Une session ProxyNinja n’est pas une session locale de votre application. À chaque arrivée sur le point d’entrée d’authentification, effectuez une nouvelle demande d’identité auprès de ProxyNinja, même si une session locale existe déjà. Ne court-circuitez pas cette entrée avec une identité ou des attributs conservés en cache : ils peuvent avoir changé depuis la connexion précédente. C’est particulièrement important avec Pronote, et cette règle s’applique aussi aux autres sources d’identité.

Après validation de la réponse, mettez à jour les attributs et les droits locaux, puis renouvelez la session applicative avant d’ouvrir la destination. Une nouvelle transaction d’authentification n’impose pas nécessairement une nouvelle saisie d’identifiants : le SSO peut rester transparent. La navigation ordinaire dans la ressource peut continuer à utiliser la session locale ; c’est l’arrivée sur l’entrée SSO qui déclenche ce renouvellement. Voir le parcours d’authentification.

Enregistrez la destination validée côté serveur dans la transaction de connexion avant de partir vers le fournisseur d’identité. En OIDC, associez-la au state aléatoire produit et vérifié par votre bibliothèque, avec le contexte PKCE/nonce habituel. Conservez les garanties de la bibliothèque ; ne remplacez pas state par l’URL de destination.

Utilisez une transaction par connexion, liée à la session du navigateur, avec expiration et consommation unique. Un champ global session.target peut être écrasé quand deux onglets ouvrent deux contenus différents.

Schéma d’intégration, en pseudo-code (les opérations d’authentification sont celles de votre bibliothèque, pas une API ProxyNinja à recopier) :

À l'entrée SSO de la ressource :
destination = valider(target), ou accueil si absent
si destination invalide : refuser la requête
même si une session locale existe déjà :
créer une nouvelle transaction d'authentification liée au navigateur
stocker destination dans cette transaction côté serveur
demander à nouveau l'identité avec le contexte sécurisé de la bibliothèque
Au callback de la ressource :
valider la réponse et la transaction avec la bibliothèque
si erreur ou transaction absente/expirée : arrêter le parcours
récupérer la destination depuis la transaction validée, pas depuis le callback
actualiser les attributs et les droits, renouveler la session locale
consommer la transaction
vérifier les droits sur le contenu
rediriger vers la destination sans recopier code, state ou jetons

Même si la landing ajoute un code à votre URL d’entrée, n’échangez pas un code émis pour le client de la landing avec les identifiants de votre propre client. Démarrez une nouvelle transaction de votre ressource à chaque entrée SSO ; la session SSO déjà établie peut éviter une nouvelle saisie d’identifiants.

Préférez un callback fixe pour la transaction OIDC de votre ressource : stockez la destination côté serveur au lieu de modifier ce callback à chaque cours. Si votre intégration utilise effectivement un callback avec target dans sa query string, la redirect_uri de l’échange de code doit être identique à celle envoyée lors de l’autorisation, y compris target ; elle n’inclut pas les paramètres de réponse code et state. (OAuth 2.0, section 4.1.3)

Faites valider avec ProxyNinja l’acceptation de la nouvelle URL de retour de la landing par son client Keycloak. L’ajout de target ne justifie pas d’autoriser tous les domaines ou tous les chemins de callback.

Redirigez vers la destination validée une fois l’identité vérifiée et la session locale créée. Le contenu applique ses contrôles d’accès habituels. Si le cours est inexistant ou interdit, affichez votre réponse 404/403 habituelle ; ne renvoyez pas automatiquement vers la landing, ce qui pourrait créer une boucle.

Test Résultat attendu
Lien sans target Parcours et accueil existants inchangés.
/cours/42 sans session locale Authentification, création de session, puis cours 42.
/cours/42 avec session locale Nouvelle demande d’identité, attributs actualisés, session renouvelée, puis cours 42.
Attributs Pronote modifiés depuis la connexion précédente À la prochaine arrivée sur l’entrée SSO, les nouveaux attributs et droits remplacent les valeurs locales précédentes.
/cours/42?chapter=2#exercise Paramètres et ancre conservés, sans double décodage.
URL absolue sur votre origine Acceptée si le chemin est autorisé.
URL externe, //autre.example, javascript:… Refus, sans redirection externe.
Deux onglets avec deux cours Chaque transaction conserve sa propre destination.
Connexion refusée ou transaction expirée Aucun accès au contenu ni utilisation d’une ancienne destination.
Contenu interdit à l’utilisateur Refus d’accès, même avec une session valide.

Vérifiez les deux étapes : target est présent dans l’URL reçue par votre point d’entrée, puis il est encore disponible après votre retour d’authentification. N’insérez ni secret ni donnée personnelle dans target : une URL peut apparaître dans l’historique et les journaux d’accès.