Post

Google SSO - Integration

A writeup on integrating Google SSO with Captiveportal.

Google SSO - Integration

Google SSO - Integration

A writeup on integrating Google SSO with Captiveportal.

Date: September 18, 2025
Categories: Programming, Linux
Tags: linux, cheatsheet, terminal


πŸ“– Overview

This project integrates Google Single Sign-On (SSO) authentication into a WiFi captive portal controlled by CoovaChilli and FreeRADIUS.

It allows users to access the Internet using:

  • βœ… Google Workspace login (OAuth2)
  • βœ… Username & Password (RADIUS)
  • βœ… Coupon/Voucher login

After successful authentication, users are logged into CoovaChilli and redirected to a branded success page.


πŸ”§ Components Used

  • CoovaChilli β†’ Captive portal / access controller
  • FreeRADIUS β†’ AAA server (Authentication, Authorization, Accounting)
  • MySQL (MariaDB) β†’ RADIUS database
  • PHP (daloRADIUS based pages) β†’ Custom login / SSO integration
  • Google OAuth 2.0 β†’ Identity provider for authentication

πŸ“ Directory Layout

1
2
3
4
5
6
7
8
9
10
/var/www/html/hotspotlogin/
β”‚
β”œβ”€β”€ config.php             # Centralized configuration
β”œβ”€β”€ login-choice.php       # Landing page for method selection
β”œβ”€β”€ google-sso.php         # Initiates Google OAuth
β”œβ”€β”€ sso-callback.php       # Handles Google response, updates RADIUS
β”œβ”€β”€ hotspotlogin.php       # Regular username/password or coupon login
β”œβ”€β”€ success.php            # Final success page
β”œβ”€β”€ logoff.php             # Handles logout
└── assets/                # (Optional) images, icons, CSS

βš™οΈ Configuration File

config.php

This file stores all deployment-specific settings β€” Google credentials, CoovaChilli IPs, DB credentials, and domain/group mappings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
return [

    // ===== GOOGLE OAUTH CONFIG =====
    'google_client_id'     => 'YOUR_CLIENT_ID.apps.googleusercontent.com',
    'google_client_secret' => 'YOUR_GOOGLE_CLIENT_SECRET',
    'google_redirect_uri'  => 'https://yourdomain.com/hotspotlogin/sso-callback.php',

    // ===== COOVACHILLI CONFIG =====
    'uam_secret' => 'testing123',      // Shared secret between portal & CoovaChilli
    'uam_ip'     => '172.27.0.1',
    'uam_port'   => '3990',

    // ===== RADIUS DATABASE CONFIG =====
    'db_host' => 'localhost',
    'db_name' => 'radius',
    'db_user' => 'radiususer',
    'db_pass' => 'radiuspass',

    // ===== DOMAIN β†’ RADIUS GROUP MAPPING =====
    'allowed_domains' => [
        'amyntortech.com'  => 'google_sso_students',
        'clientdomain.com' => 'google_sso_staff'
    ],
];

When deploying to a new client:

  • Replace Google Client ID, Secret, and Redirect URI
  • Update domain mappings
  • Update RADIUS DB credentials and UAM IP/port

🧭 Login Flow

πŸ”Ή Step 1 β€” User Chooses Login Method

File: login-choice.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
if (session_status() === PHP_SESSION_NONE) session_start();

// Store CoovaChilli parameters
$params = ['uamip','uamport','challenge','called','mac','ip','nasid','sessionid','userurl','res'];
foreach ($params as $p) $_SESSION[$p] = $_GET[$p] ?? $_SESSION[$p] ?? '';

$uamip   = $_SESSION['uamip'] ?: '172.27.0.1';
$uamport = $_SESSION['uamport'] ?: '3990';

$session_params = http_build_query([
    'uamip' => $uamip, 'uamport' => $uamport,
    'challenge' => $_SESSION['challenge'] ?? '',
    'mac' => $_SESSION['mac'] ?? '',
    'ip' => $_SESSION['ip'] ?? '',
    'nasid' => $_SESSION['nasid'] ?? '',
    'sessionid' => $_SESSION['sessionid'] ?? '',
    'res' => 'notyet'
]);
?>
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>WiFi Login</title>
    <style>
        body { font-family: sans-serif; background: linear-gradient(135deg,#667eea,#764ba2);
               min-height:100vh;display:flex;justify-content:center;align-items:center;}
        .container { background:#fff; border-radius:16px; padding:48px; max-width:420px; width:100%;
                     box-shadow:0 20px 60px rgba(0,0,0,0.3);}
        .login-btn { display:flex; align-items:center; justify-content:center; gap:12px; padding:16px;
                     border-radius:8px; text-decoration:none; margin-bottom:12px; font-weight:600;}
        .google-btn { background:white; color:#111; border:1px solid #ddd;}
        .credential-btn { background:#667eea; color:white;}
        .coupon-btn { background:#10b981; color:white;}
    </style>
</head>
<body>
<div class="container">
    <h2 style="text-align:center;">Welcome</h2>
    <p style="text-align:center;">Choose your login method</p>
    <a href="google-sso.php?<?= $session_params ?>" class="login-btn google-btn">Continue with Google</a>
    <a href="hotspotlogin.php?<?= $session_params ?>" class="login-btn credential-btn">Username & Password</a>
    <a href="hotspotlogin.php?<?= $session_params ?>&login-type=coupon" class="login-btn coupon-btn">Coupon Code</a>
</div>
</body>
</html>

πŸ”Ή Step 2 β€” Start Google OAuth

File: google-sso.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if (session_status() === PHP_SESSION_NONE) session_start();
$config = include('config.php');

$client_id = $config['google_client_id'];
$redirect  = $config['google_redirect_uri'];
$scope     = 'openid email profile';

$auth_url = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
    'client_id' => $client_id,
    'redirect_uri' => $redirect,
    'response_type' => 'code',
    'scope' => $scope,
    'access_type' => 'offline',
    'prompt' => 'select_account',
]);

header("Location: $auth_url");
exit;

πŸ”Ή Step 3 β€” Handle Callback

File: sso-callback.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
if (session_status() === PHP_SESSION_NONE) session_start();
$config = include('config.php');

$client_id     = $config['google_client_id'];
$client_secret = $config['google_client_secret'];
$redirect_uri  = $config['google_redirect_uri'];

// Get auth code
if (!isset($_GET['code'])) die("Authorization code missing.");

// Exchange code for tokens
$ch = curl_init('https://oauth2.googleapis.com/token');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => [
        'code' => $_GET['code'],
        'client_id' => $client_id,
        'client_secret' => $client_secret,
        'redirect_uri' => $redirect_uri,
        'grant_type' => 'authorization_code',
    ],
]);
$response = curl_exec($ch);
$tokens = json_decode($response, true);
curl_close($ch);

if (!isset($tokens['id_token'])) die("No ID token received.");

// Decode JWT payload
$parts = explode('.', $tokens['id_token']);
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

$email     = $payload['email'] ?? '';
$firstname = $payload['given_name'] ?? strtok($email, '@');
$lastname  = $payload['family_name'] ?? '';

if (!$email) die("Email missing in ID token.");

// Connect to RADIUS DB
$db = new mysqli($config['db_host'], $config['db_user'], $config['db_pass'], $config['db_name']);
if ($db->connect_error) die("DB connection failed: " . $db->connect_error);

$domain = substr(strrchr($email, "@"), 1);
$group  = $config['allowed_domains'][$domain] ?? 'google_sso_default';

// Insert/update user in radcheck
$db->query("REPLACE INTO radcheck (username, attribute, op, value)
            VALUES ('$email', 'Cleartext-Password', ':=', 'googlepass')");

// Map group
$db->query("REPLACE INTO radusergroup (username, groupname, priority)
            VALUES ('$email', '$group', 1)");

$_SESSION['sso_login_user'] = $email;

// Redirect to CoovaChilli login
$uamip   = $_SESSION['uamip'] ?? $config['uam_ip'];
$uamport = $_SESSION['uamport'] ?? $config['uam_port'];
$userurl = urlencode("https://{$_SERVER['HTTP_HOST']}/hotspotlogin/success.php?username=$email");

header("Location: http://$uamip:$uamport/login?username=$email&password=googlepass&userurl=$userurl");
exit;

πŸ”Ή Step 4 β€” Success Page

File: success.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if (session_status() === PHP_SESSION_NONE) session_start();
$username = $_GET['username'] ?? $_SESSION['sso_login_user'] ?? 'Guest';
?>
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Login Successful</title></head>
<body style="font-family:sans-serif;text-align:center;margin-top:50px;">
    <h2>Welcome, <?= htmlspecialchars($username) ?>!</h2>
    <p>You are now connected to the Internet.</p>
    <a href="logoff.php">Logout</a>
</body>
</html>

πŸ”Ή Step 5 β€” Logoff

File: logoff.php

1
2
3
4
5
6
7
8
9
10
<?php
if (session_status() === PHP_SESSION_NONE) session_start();
$config = include('config.php');

$uamip   = $_GET['uamip'] ?? $config['uam_ip'];
$uamport = $_GET['uamport'] ?? $config['uam_port'];

session_destroy();
header("Location: http://$uamip:$uamport/logoff");
exit;

🌍 Deployment Steps for a New Client

  1. Copy /hotspotlogin/ folder to new server
  2. Create config.php and update:
    • Google client credentials
    • Redirect URI
    • DB credentials
    • Domain/group mapping
    • CoovaChilli UAM IP/port
  3. Set Google OAuth redirect URI in Google Cloud Console:
    https://yourdomain.com/hotspotlogin/sso-callback.php
  4. Restart Apache and CoovaChilli
  5. Test login via captive portal: http://172.27.0.1:3990

πŸ” Security Tips

  • Always use HTTPS for the portal domain
  • Restrict Google OAuth to your Workspace domain via the hd parameter
  • Use a strong uam_secret
  • Keep Google client secrets out of version control

🧩 Debugging

Check logs:

1
2
tail -f /var/log/apache2/error.log
tail -f /var/log/freeradius/radius.log

βœ… Summary of Editable Files

File Edit Needed Purpose
config.php βœ… Yes Core configuration
login-choice.php Optional UI customization
google-sso.php No (reads config) Starts Google login
sso-callback.php No (reads config) Processes Google login
success.php Optional Branding
logoff.php No Handles logout

🎯 User Flow Summary

  1. User connects β†’ redirected to login-choice.php
  2. Chooses β€œLogin with Google”
  3. Google authenticates β†’ returns to sso-callback.php
  4. User added to RADIUS DB β†’ redirected to CoovaChilli login
  5. Coova grants access β†’ success.php
  6. User can log off via logoff.php

πŸ–₯️ Example Redirects

Stage URL Example
Google Login Start /hotspotlogin/google-sso.php
OAuth Callback /hotspotlogin/sso-callback.php?code=...
Coova Login http://172.27.0.1:3990/login?username=...
Success /hotspotlogin/success.php?username=...
Logoff /hotspotlogin/logoff.php

πŸ“Š Network Flow Summary

Client β†’ CoovaChilli β†’ Captive Portal β†’ Google OAuth β†’ CoovaChilli β†’ Internet

1
2
3
4
5
6
7
8
9
10
11
12
13
User Device
   ↓
CoovaChilli Gateway
   ↓ (Redirect)
hotspotlogin/login-choice.php
   ↓
Google OAuth (google-sso.php β†’ sso-callback.php)
   ↓
FreeRADIUS DB Update
   ↓
CoovaChilli Login (grants IP access)
   ↓
success.php β†’ Internet access

🧠 Notes

  • Google Workspace admins must preapprove your redirect URI
  • If captive portal uses HTTP only, Chrome may block Google OAuth β†’ always use HTTPS
  • β€œAuthorization code already used” errors are normal if users refresh the callback URL

✍️ Author

Implementation & integration by HacktheProtocol Infosec team.

This document explains the Google SSO captive portal deployment step by step.

This post is licensed under CC BY 4.0 by the author.