Exemples de code
Exemples complets et prêts à l'emploi pour intégrer l'authentification Octal Group dans vos projets.
PHP Vanilla — Flux complet
Trois fichiers : login.php, callback.php, dashboard.php
login.php — Initier la connexion
<?php session_start(); // Configuration define('OCTAL_BASE', 'https://account.groupeoctal.com'); define('CLIENT_ID', 'YOUR_CLIENT_ID'); define('CLIENT_SECRET', 'YOUR_CLIENT_SECRET'); define('REDIRECT_URI', 'https://votre-app.com/callback.php'); // Générer state et PKCE $state = bin2hex(random_bytes(16)); $codeVerifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '='); $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '='); $_SESSION['oauth_state'] = $state; $_SESSION['oauth_code_verifier'] = $codeVerifier; $url = OCTAL_BASE . '/oauth/authorize?' . http_build_query([ 'client_id' => CLIENT_ID, 'redirect_uri' => REDIRECT_URI, 'response_type' => 'code', 'scope' => 'openid profile email', 'state' => $state, 'code_challenge' => $codeChallenge, 'code_challenge_method' => 'S256', ]); header('Location: ' . $url); exit;
callback.php — Traiter le retour
<?php session_start(); // Vérifier le state (anti-CSRF) if ($_GET['state'] !== $_SESSION['oauth_state']) { die('State mismatch - possible CSRF attack'); } $code = $_GET['code']; $codeVerifier = $_SESSION['oauth_code_verifier']; // Échange code → token $ch = curl_init(OCTAL_BASE . '/oauth/token'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => REDIRECT_URI, 'client_id' => CLIENT_ID, 'client_secret' => CLIENT_SECRET, 'code_verifier' => $codeVerifier, ]), ]); $tokenData = json_decode(curl_exec($ch), true); curl_close($ch); // Récupérer le profil $ch = curl_init(OCTAL_BASE . '/oauth/userinfo'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $tokenData['access_token']], ]); $user = json_decode(curl_exec($ch), true); curl_close($ch); // Créer la session utilisateur $_SESSION['user'] = [ 'id' => $user['sub'], 'name' => $user['name'], 'email' => $user['email'], 'avatar' => $user['avatar'], ]; header('Location: /dashboard.php'); exit;
Laravel — Intégration complète
Controller + routes pour Laravel 10/11
app/Http/Controllers/OctalAuthController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Str; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Auth; use App\Models\User; class OctalAuthController extends Controller { private string $baseUrl = 'https://account.groupeoctal.com'; private string $clientId = ''; // depuis .env public function __construct() { $this->clientId = config('services.octal.client_id'); $this->clientSecret = config('services.octal.client_secret'); $this->redirectUri = config('services.octal.redirect'); } public function redirect() { $state = Str::random(40); session(['octal_state' => $state]); return redirect($this->baseUrl . '/oauth/authorize?' . http_build_query([ 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'response_type' => 'code', 'scope' => 'openid profile email', 'state' => $state, ])); } public function callback(Request $request) { if ($request->state !== session('octal_state')) { abort(403, 'State mismatch'); } $token = Http::asForm()->post($this->baseUrl . '/oauth/token', [ 'grant_type' => 'authorization_code', 'code' => $request->code, 'redirect_uri' => $this->redirectUri, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, ])->json(); $profile = Http::withToken($token['access_token']) ->get($this->baseUrl . '/oauth/userinfo') ->json(); $user = User::updateOrCreate( ['octal_id' => $profile['sub']], [ 'name' => $profile['name'], 'email' => $profile['email'], 'avatar' => $profile['avatar'] ?? null, 'access_token' => $token['access_token'], 'refresh_token' => $token['refresh_token'] ?? null, ] ); Auth::login($user); return redirect('/dashboard'); } }
routes/web.php
Route::get('/auth/octal', [OctalAuthController::class, 'redirect'])->name('octal.login'); Route::get('/auth/octal/callback', [OctalAuthController::class, 'callback'])->name('octal.callback');
config/services.php — Ajouter
'octal' => [ 'client_id' => env('OCTAL_CLIENT_ID'), 'client_secret' => env('OCTAL_CLIENT_SECRET'), 'redirect' => env('OCTAL_REDIRECT_URI'), ],
Node.js + Express — Flux complet
Utilise axios et express-session
const express = require('express'); const session = require('express-session'); const axios = require('axios'); const crypto = require('crypto'); const app = express(); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true })); const OCTAL_BASE = 'https://account.groupeoctal.com'; const CLIENT_ID = process.env.OCTAL_CLIENT_ID; const CLIENT_SECRET = process.env.OCTAL_CLIENT_SECRET; const REDIRECT_URI = process.env.OCTAL_REDIRECT_URI; // Route : initier la connexion app.get('/auth/octal', (req, res) => { const state = crypto.randomBytes(16).toString('hex'); req.session.octalState = state; const params = new URLSearchParams({ client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, response_type: 'code', scope: 'openid profile email', state, }); res.redirect(`${OCTAL_BASE}/oauth/authorize?${params}`); }); // Route : callback app.get('/auth/octal/callback', async (req, res) => { const { code, state } = req.query; if (state !== req.session.octalState) return res.status(403).send('State mismatch'); try { // Échanger code contre token const { data: token } = await axios.post( `${OCTAL_BASE}/oauth/token`, new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } ); // Récupérer le profil const { data: user } = await axios.get( `${OCTAL_BASE}/oauth/userinfo`, { headers: { Authorization: `Bearer ${token.access_token}` } } ); req.session.user = user; res.redirect('/dashboard'); } catch (err) { console.error(err.response?.data || err.message); res.status(500).send('Authentication error'); } }); app.listen(3000);
Python + Flask — Flux complet
Utilise requests et flask
from flask import Flask, redirect, request, session, url_for import requests, secrets, os app = Flask(__name__) app.secret_key = os.environ['SECRET_KEY'] OCTAL_BASE = 'https://account.groupeoctal.com' CLIENT_ID = os.environ['OCTAL_CLIENT_ID'] CLIENT_SECRET = os.environ['OCTAL_CLIENT_SECRET'] REDIRECT_URI = os.environ['OCTAL_REDIRECT_URI'] @app.route('/auth/octal') def octal_login(): state = secrets.token_hex(16) session['octal_state'] = state params = { 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, 'response_type': 'code', 'scope': 'openid profile email', 'state': state, } url = requests.Request('GET', f'{OCTAL_BASE}/oauth/authorize', params=params).prepare().url return redirect(url) @app.route('/auth/octal/callback') def octal_callback(): if request.args.get('state') != session.pop('octal_state', None): return 'State mismatch', 403 token_resp = requests.post(f'{OCTAL_BASE}/oauth/token', data={ 'grant_type': 'authorization_code', 'code': request.args.get('code'), 'redirect_uri': REDIRECT_URI, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, }).json() access_token = token_resp['access_token'] user = requests.get(f'{OCTAL_BASE}/oauth/userinfo', headers={'Authorization': f'Bearer {access_token}'} ).json() session['user'] = user return redirect('/dashboard') @app.route('/dashboard') def dashboard(): user = session.get('user') if not user: return redirect('/auth/octal') return f'Bienvenue, {user["name"]} ({user["email"]})'
React — Hook + Composant
Hook useOctalAuth + composant de callback
// hooks/useOctalAuth.js import { useState, useEffect } from 'react'; const OCTAL_BASE = 'https://account.groupeoctal.com'; const CLIENT_ID = process.env.REACT_APP_OCTAL_CLIENT_ID; const REDIRECT_URI = process.env.REACT_APP_OCTAL_REDIRECT_URI; export function useOctalAuth() { const [user, setUser] = useState(null); const login = () => { const state = crypto.randomUUID(); sessionStorage.setItem('octal_state', state); const url = `${OCTAL_BASE}/oauth/authorize?` + new URLSearchParams({ client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, response_type: 'code', scope: 'openid profile email', state, }); window.location.href = url; }; const logout = () => setUser(null); return { user, setUser, login, logout }; } // components/OctalCallback.jsx — Placer sur /auth/callback import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; export function OctalCallback({ setUser }) { const navigate = useNavigate(); useEffect(() => { const params = new URLSearchParams(window.location.search); const code = params.get('code'); const state = params.get('state'); if (state !== sessionStorage.getItem('octal_state')) { console.error('State mismatch'); navigate('/'); return; } // NOTE : L'échange code→token doit se faire côté serveur // Envoyez le code à votre API backend pour éviter d'exposer le client_secret fetch('/api/auth/octal', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, redirect_uri: REDIRECT_URI }), }) .then(r => r.json()) .then(user => { setUser(user); navigate('/dashboard'); }); }, []); return <div>Connexion en cours...</div>; }
Vue.js 3 — Composable + Composant
Composable useOctalAuth pour Vue 3 / Nuxt
// composables/useOctalAuth.js import { ref } from 'vue'; const OCTAL_BASE = 'https://account.groupeoctal.com'; const CLIENT_ID = import.meta.env.VITE_OCTAL_CLIENT_ID; const REDIRECT_URI = import.meta.env.VITE_OCTAL_REDIRECT_URI; export function useOctalAuth() { const user = ref(null); const loading = ref(false); const login = () => { const state = crypto.randomUUID(); sessionStorage.setItem('octal_state', state); const url = `${OCTAL_BASE}/oauth/authorize?` + new URLSearchParams({ client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, response_type: 'code', scope: 'openid profile email', state, }); window.location.href = url; }; const handleCallback = async (code, state) => { if (state !== sessionStorage.getItem('octal_state')) { throw new Error('State mismatch'); } loading.value = true; // Déléguer l'échange de token au backend const res = await fetch('/api/auth/octal', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, redirect_uri: REDIRECT_URI }), }); user.value = await res.json(); loading.value = false; sessionStorage.removeItem('octal_state'); }; return { user, loading, login, handleCallback }; } <!-- OctalSignInButton.vue --> <template> <button @click="login" :class="['octal-signin-btn', dark && 'octal-signin-btn--dark']"> <img :src="`${baseUrl}/images/logo.svg`" alt="" width="22" height="22" /> {{ label }} </button> </template> <script setup> import { useOctalAuth } from '@/composables/useOctalAuth'; const props = defineProps({ label: { type: String, default: 'Se connecter avec Octal Group' }, dark: { type: Boolean, default: false }, }); const { login } = useOctalAuth(); const baseUrl = 'https://account.groupeoctal.com'; </script>