Thanks to @anoek for helping me figure this out. We got the Oauth2 connection working! This should be the preferred method for logging in over getting a user’s username and password like almost all 3rd party apps currently do.
I apologize if my settings are not perfect or written well. I had to ChatGPT part of this and even I don’t understand how everything works lol~~ My hope is others will use this as a start and write something better to share with others.
Firstly you will need to register your application here: Play Go at online-go.com! | OGS
The redirect uri is where OGS will redirect the user after logging in.
Secondly, you will need to get your Client id from there. You will need to send this to OGS to get your code.
Next, you need to actually send the user to OGS Oauth2 link Play Go at online-go.com! | OGS with the proper parameters. Here is an example of what the url should look like.
![]()
At the top of my script I list the variable links that you will need to use.

Important!! The urls must match EXACTLY this included the / at the end of /token/. Your redirect_uri also must match exactly to what you input in your application settings above. Including the /
This is the part that comes next, I don’t fully understand it but it creates some of the parameters needed. (They need unique parameters to match the callbacks.)
Next I check the url for the code. This code will be given from OGS, but on the first time the page loads I don’t have it yet. So I handle the case where the code doesn’t exist by creating the parameters and sending everything to ogs.
// --- Check for redirect params ---
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const returnedState = params.get("state");
if (!code) {
// === Step 1: Start Login ===
const randomArray = new Uint8Array(32);
crypto.getRandomValues(randomArray);
const verifier = base64URLEncode(randomArray);
const challenge = base64URLEncode(await sha256(verifier));
const state = crypto.randomUUID();
localStorage.setItem("ogs_verifier", verifier);
localStorage.setItem("ogs_state", state);
// The important bit. The URL.
// Note we are sending it to the auth_url /authorize above.
const url = `${auth_url}?client_id=${client_id}&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&code_challenge=${challenge}&code_challenge_method=S256&state=${state}`;
window.location.href = url;
}
Next, the user should see something like this.
Once they Authorize your app, they will be sent to your redirect_uri with a code and state in the parameters.
![]()
Now you need to trade the code with OGS to get the users access_token and refresh_token. The access_token is what you will need to do all the call to ogs to play moves, accept games, ect…
Send the code to /token/ url. Don’t forget the /

else { // I have the code in the url
// === Step 2: Handle Redirect ===
const verifier = localStorage.getItem("ogs_verifier");
const originalState = localStorage.getItem("ogs_state");
// ✅ Extra guard to ensure we have what we need
if (!verifier || !originalState) {
console.error("Missing verifier or state — user may have refreshed after login.");
return;
}
if (returnedState !== originalState) {
console.error("State mismatch — possible CSRF attack.");
return;
}
const bodyData = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri,
client_id,
code_verifier: verifier
});
try {
console.log("Sending POST to:", token_url);
console.log('Body Data: ' + bodyData.toString());
const response = await fetch(token_url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyData.toString()
});
console.log("Response status:", response.status);
const data = await response.json();
// Once I have the token I handle it however I want.
// In my case, I save it to the user's local storage.
if (typeof bubble_fn_ogsToken === "function") {
let bubble = {
'output1': data.access_token,
'output2': data.expires_in,
'output3': data.refresh_token
};
bubble_fn_ogsToken(bubble);
}
} catch (err) {
console.error("Error exchanging code for token:", err);
}
}
})();
After all this you should have the access_token and refresh_token on the user’s device and should be able to do everything you need to on OGS.
For reference, this is my whole script below.
(async function() {
const client_id = "My_Client_id"; // Put your client id here.
const redirect_uri = "https://tsumegodragon.com/ogs-login";
const token_url = "https://online-go.com/oauth2/token/";
const auth_url = "https://online-go.com/oauth2/authorize";
// --- Helper functions ---
function base64URLEncode(buffer) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
async function sha256(str) {
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(str));
return buf;
}
// --- Check for redirect params ---
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const returnedState = params.get("state");
if (!code) {
// === Step 1: Start Login ===
const randomArray = new Uint8Array(32);
crypto.getRandomValues(randomArray);
const verifier = base64URLEncode(randomArray);
const challenge = base64URLEncode(await sha256(verifier));
const state = crypto.randomUUID();
localStorage.setItem("ogs_verifier", verifier);
localStorage.setItem("ogs_state", state);
const url = `${auth_url}?client_id=${client_id}&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&code_challenge=${challenge}&code_challenge_method=S256&state=${state}`;
window.location.href = url;
} else {
// === Step 2: Handle Redirect ===
const verifier = localStorage.getItem("ogs_verifier");
const originalState = localStorage.getItem("ogs_state");
// ✅ Extra guard to ensure we have what we need
if (!verifier || !originalState) {
console.error("Missing verifier or state — user may have refreshed after login.");
return;
}
if (returnedState !== originalState) {
console.error("State mismatch — possible CSRF attack.");
return;
}
const bodyData = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri,
client_id,
code_verifier: verifier
});
try {
console.log("Sending POST to:", token_url);
console.log('Body Data: ' + bodyData.toString());
const response = await fetch(token_url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyData.toString()
});
console.log("Response status:", response.status);
const data = await response.json();
if (typeof bubble_fn_ogsToken === "function") {
let bubble = {
'output1': data.access_token,
'output2': data.expires_in,
'output3': data.refresh_token
};
bubble_fn_ogsToken(bubble);
}
} catch (err) {
console.error("Error exchanging code for token:", err);
}
}
})();
As I said, this probably isn’t the cleanest method as I am no expert. Someone else should be able to make a cleaner version and will hopefully share it with others as well.
With all that being said, I hope this helps everyone who needed to login with Oauth2!


