1. What Is Google Identity Services?
Google Identity Services, often shortened to GIS, is Google’s modern browser library for adding Google sign-in to websites and web applications.
It allows a user to click a familiar Sign in with Google button. Google then authenticates the user and sends your web page a credential response. That response contains an ID token, which is a signed token containing identity information such as the user’s name, email, profile picture, and Google account ID.
The simplest flow
User opens your page
↓
User clicks Sign in with Google
↓
Google verifies the user
↓
Your page receives an ID token
↓
Your page reads user name and email
↓
Your page shows protected content
2. Why Use Google Login?
For a learning website, Google login is useful because many students already have Gmail accounts. You do not need to build a password system at the beginning.
Static website reality
GitHub Pages can host HTML, CSS, and JavaScript. It cannot secretly verify a token on the server because there is no server code running on GitHub Pages. So GitHub Pages login is useful for a front-end gate, but not for strong security.
3. Create the Google OAuth Client ID
Before your page can use Google Identity Services, you need a web application client ID from Google Cloud. Google’s setup guide says a Google API client ID is required before enabling Google Identity Services on a website.
Steps
- Open Google Cloud Console.
- Open Google Auth Platform.
- Create or select a project.
- Configure the consent screen.
- Create an OAuth client.
- Choose Web application.
- Add authorized JavaScript origins.
- Copy the client ID.
For GitHub Pages
Authorized JavaScript origin:
https://programmer-s-picnic.github.io
For your own domains
Authorized JavaScript origins:
https://www.learnwithchampak.live
https://learnwithchampak.live
https://editor.learnwithchampak.live
https://aiml.learnwithchampak.live
https://programmer-s-picnic.github.io, not
https://programmer-s-picnic.github.io/json-images/links/index.html.
4. The Minimal Google Login Page
A minimal GIS page needs three things:
- The GIS script
- Your Google OAuth client ID
- A callback function to receive the credential
<script src="https://accounts.google.com/gsi/client" async defer></script>
<div
id="g_id_onload"
data-client_id="YOUR_CLIENT_ID"
data-callback="handleCredentialResponse">
</div>
<div class="g_id_signin"></div>
<script>
function handleCredentialResponse(response) {
console.log(response.credential);
}
</script>
5. What Is the ID Token?
After successful sign-in, Google gives your JavaScript callback a credential. This credential is an ID token. It is usually a JWT, which has three parts:
header.payload.signature
| Part | Meaning |
|---|---|
| Header | Metadata about the token and signing algorithm. |
| Payload | User identity information and token claims. |
| Signature | Cryptographic proof that the token was signed by Google. |
6. Decode the User Data in the Browser
For a static demo, you can decode the token payload in JavaScript.
function parseJwt(token) {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(function(c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
return JSON.parse(jsonPayload);
}
function handleCredentialResponse(response) {
const user = parseJwt(response.credential);
console.log(user.name);
console.log(user.email);
console.log(user.picture);
}
Common useful fields
| Field | Meaning |
|---|---|
name |
User’s display name. |
email |
User’s email address. |
picture |
User’s profile image. |
sub |
Stable Google account identifier. This is better than email for long-term identity. |
aud |
Audience. It should match your client ID in real verification. |
iss |
Issuer. It should be Google. |
exp |
Expiry time. |
7. Allow Only Selected Users
For a simple GitHub Pages gate, you can allow only selected email addresses.
const allowedEmails = [
"champaksworld@gmail.com",
"student1@gmail.com",
"student2@gmail.com"
];
if (!allowedEmails.includes(user.email)) {
alert("Sorry, this email is not allowed.");
return;
}
8. Use a Separate users.json File
Instead of editing your HTML every time you add a student, keep allowed users in a JSON file.
users.json
[
"champaksworld@gmail.com",
"student1@gmail.com",
"student2@gmail.com"
]
Load it from GitHub Pages
async function getAllowedEmails() {
const response = await fetch(
"https://programmer-s-picnic.github.io/json-images/links/users.json",
{ cache: "no-store" }
);
if (!response.ok) {
throw new Error("Could not load users.json");
}
return await response.json();
}
Use it during login
async function handleCredentialResponse(response) {
const user = parseJwt(response.credential);
const allowedEmails = await getAllowedEmails();
const normalizedEmail = String(user.email || "").toLowerCase().trim();
if (!allowedEmails.map(e => e.toLowerCase().trim()).includes(normalizedEmail)) {
alert("Sorry, this email is not allowed.");
return;
}
localStorage.setItem("google_user", JSON.stringify(user));
showUser(user);
}
users.json, that user should be able to log in
without changing the HTML page.
9. Logout Correctly
Your page may store the user in localStorage so the welcome message remains after
refresh.
On logout, remove that local data.
function logout() {
localStorage.removeItem("google_user");
if (window.google && google.accounts && google.accounts.id) {
google.accounts.id.disableAutoSelect();
}
location.reload();
}
Google’s JavaScript reference includes google.accounts.id.disableAutoSelect() for
sign-out
behavior so the sign-in flow does not immediately auto-select the same user again.
10. Deploy on GitHub Pages
Recommended files
json-images/
links/
index.html
users.json
privacy-policy.html
terms.html
Published URLs
Login page:
https://programmer-s-picnic.github.io/json-images/links/index.html
Allowed users file:
https://programmer-s-picnic.github.io/json-images/links/users.json
Google Cloud origin
https://programmer-s-picnic.github.io
11. The Security Truth
This is the most important chapter.
| Method | Security Level | Use Case |
|---|---|---|
| HTML + JavaScript only | Basic | Personalization, simple demos, non-sensitive lesson gates. |
| HTML + users.json | Basic | Easy student list management, still visible publicly. |
| Firebase Authentication | Good | Real user login, Firestore rules, student progress. |
| Backend token verification | Strong | Paid courses, certificates, admin panels, private data. |
12. Backend Verification: The Professional Way
In a production application, the browser should send the ID token to your backend. The backend should verify it using Google’s official libraries.
Google’s backend authentication guidance says the backend should verify that the ID token is
properly signed,
that the aud claim matches one of your client IDs, that the issuer is Google, and
that the token
has not expired.
Backend flow
Browser receives ID token
↓
Browser sends token to backend
↓
Backend verifies token signature
↓
Backend checks aud, iss, exp
↓
Backend creates its own session
↓
Backend serves protected data
Node.js style example
// Conceptual backend example.
// This code belongs on a server, not GitHub Pages.
import { OAuth2Client } from "google-auth-library";
const client = new OAuth2Client("YOUR_CLIENT_ID");
async function verifyGoogleToken(idToken) {
const ticket = await client.verifyIdToken({
idToken: idToken,
audience: "YOUR_CLIENT_ID"
});
const payload = ticket.getPayload();
return {
googleId: payload.sub,
email: payload.email,
name: payload.name,
picture: payload.picture
};
}
13. Firebase Authentication Path
For your learning system, Firebase Authentication is often easier than writing your own backend. You can host the front end on GitHub Pages and use Firebase for real authentication and database rules.
Recommended architecture
GitHub Pages
└── HTML, CSS, JavaScript
Firebase Authentication
└── Google login
Firestore Database
└── users
└── courses
└── progress
└── roles
└── query limits
Use Firebase when you need
- Student progress
- Role-based access
- Admin users
- Paid student lists
- Query limits such as 10 support queries per week
- Safer course dashboards
14. Complete GitHub Pages Login Code
Replace the client ID only if needed. This example loads allowed users from:
https://programmer-s-picnic.github.io/json-images/links/users.json
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Google Login on GitHub Pages</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://accounts.google.com/gsi/client" async defer></script>
<style>
body {
font-family: Arial, sans-serif;
background: #fff7ed;
color: #1f2937;
padding: 40px;
text-align: center;
}
.card {
max-width: 560px;
margin: auto;
background: white;
padding: 30px;
border-radius: 18px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
border-top: 6px solid #f97316;
}
#protectedContent {
display: none;
margin-top: 25px;
padding: 20px;
background: #fffbeb;
border-radius: 14px;
border: 1px solid #fed7aa;
text-align: left;
}
#loginError {
display: none;
margin-top: 18px;
padding: 14px;
background: #fef2f2;
color: #7f1d1d;
border: 1px solid #fecaca;
border-radius: 12px;
}
.profile-row {
display: flex;
gap: 14px;
align-items: center;
}
.profile-row img {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #fed7aa;
}
button {
margin-top: 20px;
padding: 10px 18px;
border: none;
border-radius: 10px;
background: #ea580c;
color: white;
cursor: pointer;
font-weight: bold;
}
</style>
</head>
<body>
<div class="card">
<h1>Programmers Picnic Login</h1>
<p>Sign in with Google to continue.</p>
<div
id="g_id_onload"
data-client_id="704346946203-m69uok3l8htbtgdsee0vtoul5b8v7clv.apps.googleusercontent.com"
data-callback="handleCredentialResponse">
</div>
<div
class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="signin_with"
data-shape="pill"
data-logo_alignment="left">
</div>
<div id="loginError"></div>
<div id="protectedContent">
<div class="profile-row">
<img id="userPicture" src="" alt="User profile picture" />
<div>
<h2 id="welcomeText"></h2>
<p id="userEmail"></p>
</div>
</div>
<p>You are now logged in.</p>
<p>This content is visible only after Google login.</p>
<button onclick="logout()">Logout</button>
</div>
</div>
<script>
const USERS_JSON_URL =
"https://programmer-s-picnic.github.io/json-images/links/users.json";
function showError(message) {
const box = document.getElementById("loginError");
box.style.display = "block";
box.textContent = message;
}
function clearError() {
const box = document.getElementById("loginError");
box.style.display = "none";
box.textContent = "";
}
function parseJwt(token) {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(function(c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
return JSON.parse(jsonPayload);
}
async function getAllowedEmails() {
const response = await fetch(USERS_JSON_URL, { cache: "no-store" });
if (!response.ok) {
throw new Error("Could not load users.json");
}
const emails = await response.json();
if (!Array.isArray(emails)) {
throw new Error("users.json must be an array of email addresses");
}
return emails.map(function(email) {
return String(email).toLowerCase().trim();
});
}
async function handleCredentialResponse(response) {
try {
clearError();
const user = parseJwt(response.credential);
const allowedEmails = await getAllowedEmails();
const userEmail = String(user.email || "").toLowerCase().trim();
if (!allowedEmails.includes(userEmail)) {
showError("Sorry, this email is not allowed: " + user.email);
return;
}
localStorage.setItem("google_user", JSON.stringify(user));
showUser(user);
} catch (error) {
console.error(error);
showError("Login failed. " + error.message);
}
}
function showUser(user) {
document.getElementById("protectedContent").style.display = "block";
document.getElementById("welcomeText").innerText =
"Welcome, " + (user.name || user.email);
document.getElementById("userEmail").innerText =
user.email || "";
if (user.picture) {
document.getElementById("userPicture").src = user.picture;
}
}
function logout() {
localStorage.removeItem("google_user");
if (window.google && google.accounts && google.accounts.id) {
google.accounts.id.disableAutoSelect();
}
location.reload();
}
window.onload = function() {
const savedUser = localStorage.getItem("google_user");
if (savedUser) {
showUser(JSON.parse(savedUser));
}
};
</script>
</body>
</html>
15. Final Checkpoints
Checkpoint 1: Files created
These files should exist:
index.html
users.json
privacy-policy.html
terms.html
Checkpoint 2: users.json works
Open this in the browser:
https://programmer-s-picnic.github.io/json-images/links/users.json
You should see something like:
[
"champaksworld@gmail.com"
]
Checkpoint 3: Login page opens
Open your login page:
https://programmer-s-picnic.github.io/json-images/links/index.html
You should see:
Programmers Picnic Login
Sign in with Google to continue.
[Sign in with Google button]
Checkpoint 4: Allowed user can enter
Sign in with an email listed in users.json. You should see:
Welcome, Your Name
You are now logged in.
This content is visible only after Google login.
Checkpoint 5: Unlisted user is blocked
Sign in with an email not listed in users.json. You should see:
Sorry, this email is not allowed.