-
-
Save ACPixel/bd71dc716126153e04e41700e8a8820e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require("express"); | |
const crypto = require("crypto"); | |
const router = express.Router(); | |
//Put your scopes here | |
const KICK_SCOPES = [ | |
"user:read", | |
"channel:read", | |
"chat:write", | |
"events:subscribe", | |
]; | |
const ENDPOINT = { | |
authURL: "https://id.kick.com/oauth/authorize", | |
tokenURL: "https://id.kick.com/oauth/token", | |
}; | |
//Put your redirect url here | |
const REDIRECT_URL = "http://localhost:3000/oauth/kick/callback"; | |
//Client ID in env | |
const KICK_CLIENT_ID = process.env.KICK_CLIENT_ID; | |
//Client Secret in env | |
const KICK_CLIENT_SECRET = process.env.KICK_CLIENT_SECRET; | |
// PKCE Helper Functions | |
//This is just a helper function to generate a random string | |
function generateCodeVerifier() { | |
const buffer = crypto.randomBytes(32); | |
return buffer.toString("base64url"); | |
} | |
//This is just a helper function to generate a code challenge(sha256 hash of the verifier) | |
function generateCodeChallenge(verifier) { | |
const hash = crypto.createHash("sha256").update(verifier).digest(); | |
return hash.toString("base64url"); | |
} | |
// Step 1: Generate a verifier, challenge and send the user to the auth page | |
router.get("/oauth/kick/", (req, res) => { | |
const codeVerifier = generateCodeVerifier(); | |
const codeChallenge = generateCodeChallenge(codeVerifier); | |
/* IMPORTANT: | |
This is a VERY BAD way to store the verifier. The original non-hashed verifier is needed, | |
later on when swapping the code for a token. | |
In a real application you should either store the original verifier in your own | |
database, or encrypt it with a secret before including it in the state. | |
It's not inherently wrong to store it in the state param, but if you do, | |
make sure it is encrypted with a secret and not just base64 encoded. | |
The verifier system is used to "prove" that the request for authorization was | |
started by your application, and later that the code exchange was also by your application. | |
*/ | |
const state = Buffer.from(JSON.stringify({ codeVerifier })).toString( | |
"base64", | |
); | |
//using url params to make sure that they are properly encoded | |
const authParams = new URLSearchParams({ | |
client_id: KICK_CLIENT_ID, | |
redirect_uri: REDIRECT_URL, | |
response_type: "code", | |
scope: KICK_SCOPES.join(" "), | |
state, | |
code_challenge: codeChallenge, | |
code_challenge_method: "S256", | |
}); | |
res.redirect(`${ENDPOINT.authURL}?${authParams.toString()}`); | |
}); | |
// Step 2: Handle the redirect from the auth page | |
router.get("/oauth/kick/callback", async (req, res) => { | |
const { code, state } = req.query; | |
if (!code) { | |
return res.status(400).json({ error: "Missing authorization code" }); | |
} | |
try { | |
//As stated above, just using base64 here for simplicity sake. Don't do this in production *please* | |
const { codeVerifier } = JSON.parse( | |
Buffer.from(state, "base64").toString(), | |
); | |
const tokenParams = new URLSearchParams({ | |
grant_type: "authorization_code", | |
client_id: KICK_CLIENT_ID, | |
client_secret: KICK_CLIENT_SECRET, | |
code, | |
redirect_uri: REDIRECT_URL, | |
//This is the PKCE part. It's the ORIGINAL generated code before it was sha256 hashed. | |
//This is what proves to kick that you are the one that started the original auth request | |
code_verifier: codeVerifier, | |
}); | |
const tokenResponse = await fetch(ENDPOINT.tokenURL, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded", | |
}, | |
body: tokenParams, | |
}); | |
const token = await tokenResponse.json(); | |
res.json(token); | |
} catch (error) { | |
res.status(500).json({ error: error.message }); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment