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>