Référence
API
OAuth2 Authentication

OAuth2 Authentication

Vue d'Ensemble Pour accéder à l'API CSWeb et interagir avec les données (dictionnaires, cases, fichiers), vous devez vous authentifier via OAuth2 Password Grant (RFC 6749).

Workflow d'Authentification


 1. POST /api/token Votre App > CSWeb username + password 

 < 
 2. access_token + refresh_token 

 3. GET /api/dictionaries 
 > 
 Authorization: Bearer TOKEN 

 < 
 4. Data (JSON) 

Flux Password Grant

Étapes :

  1. Demander token avec username + password
  2. Recevoir access_token + refresh_token
  3. Utiliser access_token pour requêtes API
  4. Renouveler avec refresh_token quand expiré

Tokens

TokenDurée de vieUsage
Access Token1 heure (3600s)Autoriser requêtes API
Refresh Token30 joursRenouveler access_token

Obtenir un Access Token

Endpoint

POST /api/token

Headers

Content-Type: application/x-www-form-urlencoded

Body Parameters (form-data)

ParamètreTypeRequisDescription
grant_typestringToujours password
usernamestringUsername CSWeb
passwordstringPassword CSWeb

Exemple Request

curl -X POST http://localhost:8080/api/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=password" \
 -d "username=api_user" \
 -d "password=SecurePassword123!"

Response Succès

HTTP 200 OK

{
 "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
 "token_type": "Bearer",
 "expires_in": 3600,
 "refresh_token": "def50200a1b2c3d4e5f6..."
}

Champs :

ChampTypeDescription
access_tokenstringToken JWT à utiliser pour requêtes API
token_typestringToujours Bearer
expires_inintegerDurée de validité en secondes (3600 = 1h)
refresh_tokenstringToken pour renouvellement

Response Erreur

HTTP 401 Unauthorized

{
 "error": "invalid_grant",
 "error_description": "The user credentials were incorrect."
}

Causes courantes :

  • Username ou password incorrect
  • Compte utilisateur désactivé
  • Permissions insuffisantes

Refresh Token

Quand Renouveler ?

  • Avant expiration : Renouveler 5 minutes avant expires_in
  • Après 401 : Si requête API retourne 401, renouveler
  • Jamais : Ne pas attendre l'expiration complète

Endpoint

POST /api/token

Body Parameters (form-data)

ParamètreTypeRequisDescription
grant_typestringToujours refresh_token
refresh_tokenstringRefresh token précédemment reçu

Exemple Request

curl -X POST http://localhost:8080/api/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=refresh_token" \
 -d "refresh_token=def50200a1b2c3d4e5f6..."

Response Refresh

HTTP 200 OK

{
 "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
 "token_type": "Bearer",
 "expires_in": 3600,
 "refresh_token": "abc12345new_refresh_token..."
}

Utiliser le Token

Header Authorization

Format :

Authorization: Bearer YOUR_ACCESS_TOKEN

Exemples Requêtes API

1. Lister Dictionnaires

curl -X GET http://localhost:8080/api/dictionnaires \
 -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

Response :

[
 {
 "name": "SURVEY_DICT",
 "label": "survey",
 "version": "1.0"
 },
 {
 "name": "CENSUS_DICT",
 "label": "census",
 "version": "2.0"
 }
]

2. Télécharger Fichier

curl -X GET http://localhost:8080/api/files/media/photo_001.jpg/content \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
 --output photo_001.jpg

3. Récupérer Cases

curl -X GET http://localhost:8080/api/dictionaries/SURVEY_DICT/cases \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Implémentation Backend

Spring Boot (Java)

Configuration (application.yml)

csweb:
 api:
 url: http://localhost:8080/api username: api_user password: SecurePassword123!
 grant-type: password

Properties Class

package com.example.config;
 
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
 
@Data
@Component
@ConfigurationProperties(prefix = "csweb.api")
public class CSWebProperties {
 private String url;
 private String username;
 private String password;
 private String grantType = "password";
}

Token Response DTO

package com.example.dto;
 
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
 
@Data
public class CSWebTokenResponse {
 @JsonProperty("access_token")
 private String accessToken;
 
 @JsonProperty("token_type")
 private String tokenType;
 
 @JsonProperty("expires_in")
 private Integer expiresIn;
 
 @JsonProperty("refresh_token")
 private String refreshToken;
}

Service d'Authentification

package com.example.service;
 
import com.example.config.CSWebProperties;
import com.example.dto.CSWebTokenResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
 
import java.time.Instant;
import java.util.concurrent.locks.ReentrantLock;
 
@Slf4j
@Service
@RequiredArgsConstructor
public class CSWebAuthService {
 
 private final CSWebProperties cswebProperties;
 private final RestTemplate restTemplate;
 
 private String currentAccessToken;
 private String currentRefreshToken;
 private Instant tokenExpiresAt;
 private final ReentrantLock lock = new ReentrantLock();
 
 /**
 * Obtenir access token valide (avec auto-refresh)
 */
 public String getAccessToken() {
 lock.lock();
 try {
 // Vérifier si token existe et est valide if (isTokenValid()) {
 return currentAccessToken;
 }
 
 // Refresh si refresh_token existe if (currentRefreshToken != null) {
 log.info("Refreshing access token");
 refreshAccessToken();
 } else {
 // Première authentification log.info("Authenticating with CSWeb");
 authenticate();
 }
 
 return currentAccessToken;
 
 } finally {
 lock.unlock();
 }
 }
 
 /**
 * Authentification initiale
 */
 private void authenticate() {
 String tokenUrl = cswebProperties.getUrl() + "/token";
 
 HttpHeaders headers = new HttpHeaders();
 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 
 MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
 body.add("grant_type", cswebProperties.getGrantType());
 body.add("username", cswebProperties.getUsername());
 body.add("password", cswebProperties.getPassword());
 
 HttpEntity<MultiValueMap<String, String>> request =
 new HttpEntity<>(body, headers);
 
 ResponseEntity<CSWebTokenResponse> response =
 restTemplate.postForEntity(tokenUrl, request, CSWebTokenResponse.class);
 
 if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
 updateTokens(response.getBody());
 log.info("Successfully authenticated with CSWeb");
 } else {
 throw new RuntimeException("Failed to authenticate with CSWeb");
 }
 }
 
 /**
 * Renouveler access token
 */
 private void refreshAccessToken() {
 String tokenUrl = cswebProperties.getUrl() + "/token";
 
 HttpHeaders headers = new HttpHeaders();
 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 
 MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
 body.add("grant_type", "refresh_token");
 body.add("refresh_token", currentRefreshToken);
 
 HttpEntity<MultiValueMap<String, String>> request =
 new HttpEntity<>(body, headers);
 
 try {
 ResponseEntity<CSWebTokenResponse> response =
 restTemplate.postForEntity(tokenUrl, request, CSWebTokenResponse.class);
 
 if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
 updateTokens(response.getBody());
 log.info("Successfully refreshed access token");
 } else {
 log.warn("Refresh failed, re-authenticating");
 currentRefreshToken = null;
 authenticate();
 }
 } catch (Exception e) {
 log.error("Refresh error, re-authenticating", e);
 currentRefreshToken = null;
 authenticate();
 }
 }
 
 /**
 * Mettre à jour tokens
 */
 private void updateTokens(CSWebTokenResponse tokenResponse) {
 this.currentAccessToken = tokenResponse.getAccessToken();
 this.currentRefreshToken = tokenResponse.getRefreshToken();
 
 // Calculer expiration (expires_in - 5 minutes de marge)
 int expiresIn = tokenResponse.getExpiresIn() != null ?
 tokenResponse.getExpiresIn() : 3600;
 this.tokenExpiresAt = Instant.now().plusSeconds(expiresIn - 300);
 }
 
 /**
 * Vérifier si token est valide
 */
 private boolean isTokenValid() {
 return currentAccessToken != null
 && tokenExpiresAt != null
 && Instant.now().isBefore(tokenExpiresAt);
 }
 
 /**
 * Forcer refresh (utile pour tests)
 */
 public void forceRefresh() {
 lock.lock();
 try {
 if (currentRefreshToken != null) {
 refreshAccessToken();
 } else {
 authenticate();
 }
 } finally {
 lock.unlock();
 }
 }
 
 /**
 * Invalider token (logout)
 */
 public void invalidate() {
 lock.lock();
 try {
 this.currentAccessToken = null;
 this.currentRefreshToken = null;
 this.tokenExpiresAt = null;
 log.info("Tokens invalidated");
 } finally {
 lock.unlock();
 }
 }
}

Service API CSWeb

package com.example.service;
 
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
 
import java.util.List;
 
@Slf4j
@Service
@RequiredArgsConstructor
public class CSWebApiService {
 
 private final CSWebAuthService authService;
 private final CSWebProperties cswebProperties;
 private final RestTemplate restTemplate;
 
 /**
 * GET request avec authentification
 */
 public <T> ResponseEntity<T> get(String endpoint, Class<T> responseType) {
 String url = cswebProperties.getUrl() + endpoint;
 HttpHeaders headers = createAuthHeaders();
 HttpEntity<?> request = new HttpEntity<>(headers);
 
 return restTemplate.exchange(url, HttpMethod.GET, request, responseType);
 }
 
 /**
 * POST request avec authentification
 */
 public <T> ResponseEntity<T> post(String endpoint, Object body,
 Class<T> responseType) {
 String url = cswebProperties.getUrl() + endpoint;
 HttpHeaders headers = createAuthHeaders();
 HttpEntity<?> request = new HttpEntity<>(body, headers);
 
 return restTemplate.exchange(url, HttpMethod.POST, request, responseType);
 }
 
 /**
 * Télécharger fichier
 */
 public byte[] downloadFile(String filename) {
 String endpoint = "/files/media/" + filename + "/content";
 String url = cswebProperties.getUrl() + endpoint;
 
 HttpHeaders headers = createAuthHeaders();
 headers.setAccept(List.of(MediaType.APPLICATION_OCTET_STREAM));
 
 HttpEntity<?> request = new HttpEntity<>(headers);
 
 ResponseEntity<byte[]> response = restTemplate.exchange(
 url, HttpMethod.GET, request, byte[].class
 );
 
 return response.getBody();
 }
 
 /**
 * Créer headers avec Authorization Bearer
 */
 private HttpHeaders createAuthHeaders() {
 String accessToken = authService.getAccessToken();
 
 HttpHeaders headers = new HttpHeaders();
 headers.set("Authorization", "Bearer " + accessToken);
 headers.setContentType(MediaType.APPLICATION_JSON);
 
 return headers;
 }
}

Exemple d'Utilisation

package com.example.controller;
 
import com.example.service.CSWebApiService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class DictionaryController {
 
 private final CSWebApiService cswebApiService;
 
 @GetMapping("/dictionaries")
 public String[] getDictionaries() {
 ResponseEntity<String[]> response = cswebApiService.get(
 "/dictionaries",
 String[].class
 );
 
 return response.getBody();
 }
 
 @GetMapping("/files/\{filename\}")
 public byte[] downloadFile(@PathVariable String filename) {
 return cswebApiService.downloadFile(filename);
 }
}

Laravel (PHP)

.env

CSWEB_API_URL=http://localhost:8080/api
CSWEB_USERNAME=api_user
CSWEB_PASSWORD=SecurePassword123!

config/services.php

return [
 'csweb' => [
 'api_url' => env('CSWEB_API_URL'),
 'username' => env('CSWEB_USERNAME'),
 'password' => env('CSWEB_PASSWORD'),
 ],
];

Service d'Authentification

<?php namespace App\Services;
 
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
 
class CSWebAuthService
{
 private string $apiUrl;
 private string $username;
 private string $password;
 
 public function __construct()
 {
 $this->apiUrl = config('services.csweb.api_url');
 $this->username = config('services.csweb.username');
 $this->password = config('services.csweb.password');
 }
 
 /**
 * Obtenir access token valide
 */
 public function getAccessToken(): string
 {
 // Vérifier cache
 $cachedToken = Cache::get('csweb_access_token');
 if ($cachedToken) {
 return $cachedToken;
 }
 
 // Authentifier return $this->authenticate();
 }
 
 /**
 * Authentification
 */
 private function authenticate(): string
 {
 $response = Http::asForm()->post("{$this->apiUrl}/token", [
 'grant_type' => 'password',
 'username' => $this->username,
 'password' => $this->password,
 ]);
 
 if ($response->failed()) {
 Log::error('CSWeb authentication failed', [
 'status' => $response->status(),
 'body' => $response->body(),
 ]);
 throw new \Exception('Failed to authenticate with CSWeb');
 }
 
 $data = $response->json();
 
 // Sauvegarder tokens en cache
 $expiresIn = $data['expires_in'] ?? 3600;
 Cache::put('csweb_access_token', $data['access_token'], $expiresIn - 300);
 Cache::put('csweb_refresh_token', $data['refresh_token'], 30 * 24 * 3600);
 
 Log::info('CSWeb authentication successful');
 
 return $data['access_token'];
 }
 
 /**
 * Refresh token
 */
 public function refreshToken(): string
 {
 $refreshToken = Cache::get('csweb_refresh_token');
 
 if (!$refreshToken) {
 return $this->authenticate();
 }
 
 $response = Http::asForm()->post("{$this->apiUrl}/token", [
 'grant_type' => 'refresh_token',
 'refresh_token' => $refreshToken,
 ]);
 
 if ($response->failed()) {
 Log::warning('CSWeb refresh failed, re-authenticating');
 return $this->authenticate();
 }
 
 $data = $response->json();
 
 // Mettre à jour cache
 $expiresIn = $data['expires_in'] ?? 3600;
 Cache::put('csweb_access_token', $data['access_token'], $expiresIn - 300);
 Cache::put('csweb_refresh_token', $data['refresh_token'], 30 * 24 * 3600);
 
 Log::info('CSWeb token refreshed');
 
 return $data['access_token'];
 }
 
 /**
 * Invalider tokens
 */
 public function invalidate(): void
 {
 Cache::forget('csweb_access_token');
 Cache::forget('csweb_refresh_token');
 }
}

Service API

<?php namespace App\Services;
 
use Illuminate\Support\Facades\Http;
 
class CSWebApiService
{
 private CSWebAuthService $authService;
 private string $apiUrl;
 
 public function __construct(CSWebAuthService $authService)
 {
 $this->authService = $authService;
 $this->apiUrl = config('services.csweb.api_url');
 }
 
 /**
 * GET request avec authentification
 */
 public function get(string $endpoint): array
 {
 $token = $this->authService->getAccessToken();
 
 $response = Http::withToken($token)->get("{$this->apiUrl}{$endpoint}");
 
 return $response->json();
 }
 
 /**
 * Télécharger fichier
 */
 public function downloadFile(string $filename): string
 {
 $token = $this->authService->getAccessToken();
 $endpoint = "/files/media/{$filename}/content";
 
 $response = Http::withToken($token)->get("{$this->apiUrl}{$endpoint}");
 
 return $response->body();
 }
}

Gestion des Erreurs

Erreur 401: Token Expiré

Symptôme :

{
 "error": "invalid_token",
 "error_description": "The access token provided has expired"
}

Solution :

// Interceptor RestTemplate (Spring Boot)
@Component
public class CSWebAuthInterceptor implements ClientHttpRequestInterceptor {
 
 @Override public ClientHttpResponse intercept(
 HttpRequest request,
 byte[] body,
 ClientHttpRequestExecution execution
 ) throws IOException {
 
 ClientHttpResponse response = execution.execute(request, body);
 
 // Si 401, refresh et retry if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
 authService.forceRefresh();
 
 // Retry avec nouveau token String newToken = authService.getAccessToken();
 request.getHeaders().set("Authorization", "Bearer " + newToken);
 
 return execution.execute(request, body);
 }
 
 return response;
 }
}

Erreur: Refresh Token Expiré

Symptôme :

{
 "error": "invalid_grant",
 "error_description": "The refresh token is invalid"
}

Solution : Ré-authentifier avec username/password

catch (Exception e) {
 log.warn("Refresh token expired, re-authenticating");
 currentRefreshToken = null;
 authenticate();
}

Best Practices

1. Cacher les Tokens

Bon :

// Spring Boot - Singleton service avec cache mémoire
@Service
public class CSWebAuthService {
 private String currentAccessToken; // Cache en mémoire
}

Mauvais :

// Ré-authentifier à chaque requête
public void callApi() {
 String token = authenticate(); // Trop de requêtes
 // ...
}

2. Refresh Proactif

Bon :

// Refresh 5 minutes AVANT expiration
Instant expiresAt = Instant.now().plusSeconds(expiresIn - 300);

Mauvais :

// Attendre 401 pour refresh
// Ralentit les requêtes

3. Thread-Safe

Bon :

private final ReentrantLock lock = new ReentrantLock();
 
public String getAccessToken() {
 lock.lock();
 try {
 // ...
 } finally {
 lock.unlock();
 }
}

4. Logs Sécurisés

Bon :

log.info("Authentication successful");

Mauvais :

log.info("Token: {}", accessToken); // Token en logs !

Ressources


CSWeb Community Platform v2.0 - OAuth2 API