Manual:CORS/fr

Cette page détaille l'utilisation des requêtes CORS (cross-origin resource sharing) dans le code JavaScript pour communiquer entre les wikis de différents domaines.

L'API MediaWiki qui inclue l'API Action et l'API REST prend en charge les types de requêtes CORS suivants :

  • les requêtes authentifiées qui utilisent les cookies, si $wgCrossSiteAJAXdomains est activé sur le wiki distant. C'est utile sur les sites Wikimedia, par exemple, pour permettre le téléversement d'images directement sur Commons depuis Wikipédia avec l'interface mobile.
    • comprend mais ne se limite pas aux requêtes authentifiées pour lesquelles un utilisateur connecté dans le wiki distant réalise une action au nom du wiki local
  • les requêtes authentifiées utilisant OAuth.
  • les requêtes anonymes, non authentifiées, utilisées principalement pour des actions plus limitées telles que la récupération des données disponibles publiquement.

Configuration

L'API Action et l'API REST imposent différentes contraintes pour le traitement des requêtes CORS.

Action API

Pour les requêtes à l'API Action, CORS est activé par défaut, mais l'URL de la requête doit contenir dans la partie texte de la requête, soit le paramètre origin avec une valeur appropriée, soit le paramètre crossorigin. La raison est que les requêtes POST CORS sont préarrangées et que le paramètres origin/crossorigin doit être inclus dans la requête de préarrangement.

  • Pour permettre les requêtes authentifiées en utilisant les cookies, utiliser origin et assurez-vous que la valeur correspond à l'une des valeurs initialisées dans $wgCrossSiteAJAXdomains sur le wiki distant. Notez que la valeur du paramètre d'origine doit commencer par le protocole HTTPS (par exemple https://mediawiki.org), même si $wgCrossSiteAJAXdomains accepte des valeurs sans celui-ci.
  • Pour autoriser les requêtes authentifiées avec OAuth, utiliser crossorigin avec une valeur quelconque et une entête de requête Authorization appropriée. Vous souhaiterez probablement utiliser OAuth 2 plutôt que OAuth 1.0a; voir OAuth/Pour les développeurs pour les détails. Depuis MediaWiki 1.44 change 1118583
  • Pour autoriser les requêtes anonymes de n'importe où, initialisez le paramètre origin de la chaîne de requête avec un astérisque *.

API REST

Pour permettre les requêtes authentifiées, vous avez deux possibilités :

  • utiliser l'extension OAuth (approche recommandée). L'API REST a été conçue pour être utilisée avec l'extension OAuth pour l'authentification et les autorisations des utilisateurs.
  • mettre $wgRestAllowCrossOriginCookieAuth à true pour que quelque soit l'origine spécifiée dans $wgCrossSiteAJAXdomains elle puisse émettre les cookies de session pour l'autorisation dans l'API REST.

Pour permettre les requêtes anonymes à l'API REST, $wgAllowCrossOrigin doit être initialisé avec true sur le wiki distant. Ceci initialisera Access-Control-Allow-Origin dans l'entête avec *. A la différence de l'API Action, l'API REST n'utilise pas le paramètre origine de l'URL de la requête.

Méthode JavaScript

Utiliser mediawiki.ForeignApi

Le ResourceLoader de MediaWiki offre le module mediawiki.ForeignApi qui est une extension de mediawiki.api et qui gère automatiquement les détails pôur vous. Deux constructeurs sont proposés pour activer CORS :

  • Pour les demandes à l'API Action, utiliser mw.ForeignApi. (introduit dans 1.26)
  • Pour les requêtes à l'API REST, utiliser mw.ForeignRest. (introduit dans 1.36)

Voir les exemples ci-dessous. Pour utiliser mw.ForeignApi ou mw.ForeignRest, les extensions doivent définir mediawiki.ForeignApi en tant que dépendance du module ResourceLoader dans extension.json.

Si vous utilisez mw.ForeignApi() avec une requête POST (.post()), alors origin=* sera inclus automatiquement. Si vous devez utiliser mw.ForeignApi() avec une requête GET (.get()), assurez-vous (si nécessaire) que origin=* est ajouté directement dans l'URL (et non à la chaîne de la requête).

Si c'est nécessaire pour l'action demandée (que l'utilisateur du wiki distant soit connecté), passez le paramètre assert: 'user' à get()/post(). Pour confirmer que l'utilisateur du wiki distant a un nom d'utilisateur particulier, passer le paramètre assertuser avec le nom d'utilisateur souhaité.

Utiliser Fetch

Si la demande GET peut être faite de manière anonyme, vous pouvez également utiliser Fetch (le remplacement moderne et basé sur la promesse XMLHttpRequest).

Utiliser jQuery.ajax

Si vous ne voulez pas utiliser mediawiki.api pour une raison quelconque, ou si vous êtes intéressé par connaître son fonctionnement à un niveau plus profond, voici comment implémenter la même fonctionnalité en utilisant directement les fonctions AJAX jQuery. Vous pourriez même utiliser directement XMLHttpRequest. Les exemples sont donnés ci-dessous.

Si vous voulez que le navigateur utilise les cookies qu'il pourrait avoir concernant le domaine, afin de garder l'utilisateur actuel connecté, vous devez aussi initialiser le champ withCredentials de XMLHttpRequest à true.

Exemples

Dans les exemples ci-dessous, nous supposons que le wiki local à partir duquel la requête a été émise est www.mediawiki.org, et que le wiki distant - celui vers lequel la requête est dirigée - est en.wikipedia.org.

Utiliser mw.ForeignApi

Requêtes authentifiées

Un exemple qui vérifie si l'utilisateur est connecté sur le wiki distant :

await mw.loader.using( 'mediawiki.ForeignApi' )
const api = new mw.ForeignApi( 'https://en.wikipedia.org/w/api.php' );
const data = await api.get( { action: 'query', meta: 'userinfo' } );
alert( `Foreign user ${data.query.userinfo.name} (ID ${data.query.userinfo.id})` );

Un exemple de base d'API d'écriture. Nous demandons un jeton csrf et l'utilisons pour initialiser une préférence utilisateur persistante qu'un gadget pourra utiliser après cela :

await mw.loader.using( 'mediawiki.ForeignApi' );
const api = new mw.ForeignApi( 'https://en.wikipedia.org/w/api.php' );
const data = await api.get( { action: 'query', meta: 'tokens' } );
await api.post( {
    action: 'options',
    token: data.query.tokens.csrftoken,
    optionname: 'userjs-test',
    optionvalue: 'Hello world!'
} );

Le même exemple peut être réécrit plus succintement en utilisant quelques méthodes de mediawiki.api helper, qui sont disponibles également pour ForeignApi :

await mw.loader.using( 'mediawiki.ForeignApi' );
const api = new mw.ForeignApi( 'https://en.wikipedia.org/w/api.php' );
await api.postWithToken( 'options', {
    action: 'options',
    optionname: 'userjs-test',
    optionvalue: 'Hello world!'
} );

Requêtes anonymes

Si le wiki cible n'accepte pas les requêtes inter-origines, ou si vous n'avez pas besoin de réaliser des actions d'écriture ou de lire des informations restreintes et que vous voulez éviter la surcharge, vous pouvez alors initialiser l'option anonymous dans le constructeur mediawiki.ForeignApi :

await mw.loader.using( 'mediawiki.ForeignApi' )
const api = new mw.ForeignApi( 'https://en.wikipedia.org/w/api.php', { anonymous: true } );
...

Utiliser mw.ForeignRest

Exemple utilisant l'API REST pour obtenir le code HTML de la page principale :

var api = new mw.ForeignRest( 'https://commons.wikimedia.org/w/rest.php/v1' );
await api.get( '/page/Main_Page/html' );

Un exemple qui interroge les pages de Wikimedia Commons dont les noms de page commencent par "Test" (et qui enregistre ensuite les résultats dans le navigateur) :

var value = "Test";

var actionApi = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' );
const api = new mw.ForeignRest(
 'https://commons.wikimedia.org/w/rest.php/v1',
 actionApi,
 { anonymous: true }
);
api.get( '/search/title', {
 limit: '10',
 q: `${ encodeURIComponent( value ) }`,
 origin: '*'
} )
.done( function ( data ) {
 console.log( data );
} );

Utiliser Fetch

Get

Obtenir le nom des trois premières images de Wikimedia Commons.
var apiEndpoint = "https://commons.wikimedia.org/w/api.php";
var params = "action=query&list=allimages&ailimit=3&format=json";

/**
 * Envoyer la requête pour obtenir les images
 */
fetch( apiEndpoint + "?" + params + "&origin=*" )
.then(function(response){
    return response.json();
})
.then(function(response) {
    var allimages = response.query.allimages; // Traiter la sortie pour obtenir le nom des images
    Object.keys(allimages).forEach(function(key) {
        console.log(allimages[key].name);
    });
});

Post

Editez le bac à sable Test de Wikipedia avec un jeton OAuth 2
var apiEndpoint = 'https://test.wikipedia.org/w/api.php';
var params = {
	action: 'edit',
	title: 'Wikipedia:Sandbox',
	text: 'Hello World',
	summary: 'Hello World',
	format: 'json',
	formatversion: '2',
	crossorigin: ''
}
// Remplacer par le jeton actuel OAuth 2
var oauthToken = "OAuthAccessToken";

/**
 * Obtenir un jeton CSRF
 */
var tokenQuery = {
	action: 'query',
	meta: 'tokens',
	format: 'json',
	formatversion: '2',
	crossorigin: ''
};
var queryURL = new URL(apiEndpoint);
queryURL.search = new URLSearchParams(tokenQuery);
fetch(queryURL, {method: 'GET', headers: {'Authorization': 'Bearer ' + oauthToken}})
	.then(function(response){return response.text()})
	.then(function(data){
		try {data=JSON.parse(data);} catch (e) {console.error(e);}
		params.token = data?.query?.tokens?.csrftoken;
		if (params.token) {
			/**
			 * Modification a posteriori.
			 * Action API requires data be posted as application/x-www-form-urlencoded (URLSearchParams) or multipart/form-data, rather than application/json (T212988)
			 */
			var postBody = new URLSearchParams();
			queryURL = new URL(apiEndpoint);
			Object.keys(params).forEach( key => {
				if ( key == 'action' || key == 'origin' || key == 'crossorigin' ) {
					queryURL.searchParams.append(key, params[key]);
				} else {
					postBody.append(key, params[key]);
				}
			});
			fetch(queryURL, {method: 'POST', headers: {'Authorization': 'Bearer ' + oauthToken}, body: postBody})
				.then(function(response){return response.text()})
				.then(function(data){
					try {data=JSON.parse(data);} catch (e) {console.error(e);}
					var result = data?.edit?.result;
					if (result) {
						console.log(result);
					} else {
						console.error("Error posting edit!");
					}
				});
		} else {
			console.error("Error retrieving CSRF token!");
		}
	});

Utiliser jQuery.ajax

Un exemple qui vérifie si l'utilisateur est connecté sur le wiki distant :

const { query } = await $.ajax( {
    url: 'https://en.wikipedia.org/w/api.php',
    data: {
        action: 'query',
        meta: 'userinfo',
        format: 'json',
        origin: 'https://www.mediawiki.org'
    },
    xhrFields: {
        withCredentials: true
    },
    dataType: 'json'
} );

alert( `Foreign user ${query.userinfo.name} (ID ${query.userinfo.id})` );

Un exemple de base d'API d'écriture :

const { query } = await $.ajax( {
    url: 'https://en.wikipedia.org/w/api.php',
    data: {
        action: 'query',
        meta: 'tokens',
        format: 'json',
        origin: 'https://www.mediawiki.org'
    },
    xhrFields: {
        withCredentials: true
    },
    dataType: 'json'
} );

await $.ajax( {
    url: 'https://en.wikipedia.org/w/api.php?origin=https://www.mediawiki.org',
    method: 'POST',
    data: {
        action: 'options',
        format: 'json',
        token: query.tokens.csrftoken,
        optionname: 'userjs-test',
        optionvalue: 'Hello world!'
    },
    xhrFields: {
        withCredentials: true
    },
    dataType: 'json'
} );

Exemples d'authentification OAuth

Version de MediaWiki :
1.44
Gerrit change 1118583

Les deux exemples externes suivants montrent comment utiliser OAuth 2 pour faire des requêtes CORS authentifiées :

Extensions au mécanisme

CentralAuth

Version de MediaWiki :
1.26

CentralAuth permet à votre code d'authentifier sur le wiki distant l'utilisateur actuellement connecté sur le wiki local en utilisant un centralauthtoken. Ceci garantit que le même compte associé sera utilisé pour les actions sur les deux wikis, au contraire d'un CORS régulier (qui nécessite que l'utilisateur se soit auparavent connecté au wiki distant).

Si à la fois le wiki local et le wiki distant ont installé CentralAuth, le mécanisme mediawiki.ForeignApi est étendu de la mème manière pour gérer cela à votre place. Pour l'implémenter vous-même, voir centralauthtoken pour les instructions sur la manière d'acquérir un jeton (du wiki local) et le passer à une requête (au wiki distant).

Alternatives à CORS

Pour les requêtes anonymes vous pouvez utiliser le format JSONP à la place. Ceci est plus simple mais un peu moins sécurisé (il accède et exécute du code JavaScript arbitraire sur le wiki si bien qu'un assaillant qui prendrait la main sur le site MediaWiki aurait un vecteur XSS sur le site distant).

Voir aussi