MoinMoin Logo
  • Comments
  • Immutable Page
  • Menu
    • Navigation
    • RecentChanges
    • FindPage
    • Local Site Map
    • Help
    • HelpContents
    • HelpOnMoinWikiSyntax
    • Display
    • Attachments
    • Info
    • Raw Text
    • Print View
    • Edit
    • Load
    • Save
  • Login

Navigation

  • Start
  • Sitemap
Unknown action login.
  • PKCE

Contents

  1. PKCE
    1. Keycloak PKCE client Gemini generated
    2. Azure Entra ID PKCE client - Gemini generated

PKCE

Proof Key for Code Exchange): For mobile or Single Page Applications (SPAs) we shoulduse Authorization Code Grant with PKCE.

Keycloak PKCE client Gemini generated

  • Could you implement a javascript client with html with no nodejs need ?

   1 <!DOCTYPE html>
   2 <html lang="en">
   3 <head>
   4     <title>OAuth2 PKCE Client</title>
   5     <style>
   6         body { font-family: sans-serif; padding: 20px; line-height: 1.6; }
   7         .container { max-width: 600px; margin: auto; border: 1px solid #ccc; padding: 20px; border-radius: 8px; }
   8         pre { background: #f4f4f4; padding: 10px; overflow-x: auto; font-size: 12px; }
   9         button { padding: 10px 20px; cursor: pointer; background: #0078d4; color: white; border: none; border-radius: 4px; }
  10     </style>
  11 </head>
  12 <body>
  13 
  14 <div class="container">
  15     <h2>OAuth2 JS Client (No Node.js)</h2>
  16     <div id="auth-status">Checking status...</div>
  17     <br>
  18     <button id="login-btn" style="display:none;">Log In</button>
  19     <button id="logout-btn" style="display:none;">Log Out</button>
  20     
  21     <h3>Token Info:</h3>
  22     <pre id="token-data">Not logged in.</pre>
  23 </div>
  24 
  25 <script>
  26     // --- CONFIGURATION ---
  27     const config = {
  28         clientId: "YOUR_CLIENT_ID",
  29         authEndpoint: "https://{KEYCLOAK_OR_ENTRA_URL}/authorize",
  30         tokenEndpoint: "https://{KEYCLOAK_OR_ENTRA_URL}/token",
  31         redirectUri: window.location.origin + window.location.pathname,
  32         scope: "openid profile"
  33     };
  34 
  35     // --- PKCE HELPERS ---
  36     async function generateCodeChallenge(codeVerifier) {
  37         const encoder = new TextEncoder();
  38         const data = encoder.encode(codeVerifier);
  39         const digest = await window.crypto.subtle.digest("SHA-256", data);
  40         return btoa(String.fromCharCode(...new Uint8Array(digest)))
  41             .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  42     }
  43 
  44     function generateRandomString(length) {
  45         const array = new Uint8Array(length);
  46         window.crypto.getRandomValues(array);
  47         return btoa(String.fromCharCode(...array))
  48             .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "").substring(0, length);
  49     }
  50 
  51     // --- MAIN LOGIC ---
  52     async function init() {
  53         const params = new URLSearchParams(window.location.search);
  54         const code = params.get("code");
  55 
  56         if (code) {
  57             // 1. Exchange code for token
  58             await exchangeCodeForToken(code);
  59             window.history.replaceState({}, document.title, window.location.pathname); // Clean URL
  60         } else if (localStorage.getItem("access_token")) {
  61             // 2. Already logged in
  62             showLoggedIn();
  63         } else {
  64             // 3. Show login button
  65             document.getElementById("login-btn").style.display = "block";
  66             document.getElementById("auth-status").innerText = "Please log in.";
  67         }
  68     }
  69 
  70     document.getElementById("login-btn").onclick = async () => {
  71         const verifier = generateRandomString(64);
  72         localStorage.setItem("code_verifier", verifier);
  73         const challenge = await generateCodeChallenge(verifier);
  74 
  75         const authUrl = `${config.authEndpoint}?` + 
  76             `client_id=${config.clientId}&` +
  77             `response_type=code&` +
  78             `redirect_uri=${encodeURIComponent(config.redirectUri)}&` +
  79             `scope=${encodeURIComponent(config.scope)}&` +
  80             `code_challenge=${challenge}&` +
  81             `code_challenge_method=S256`;
  82 
  83         window.location.href = authUrl;
  84     };
  85 
  86     async function exchangeCodeForToken(code) {
  87         const verifier = localStorage.getItem("code_verifier");
  88         
  89         const response = await fetch(config.tokenEndpoint, {
  90             method: "POST",
  91             headers: { "Content-Type": "application/x-www-form-urlencoded" },
  92             body: new URLSearchParams({
  93                 grant_type: "authorization_code",
  94                 client_id: config.clientId,
  95                 code: code,
  96                 redirect_uri: config.redirectUri,
  97                 code_verifier: verifier
  98             })
  99         });
 100 
 101         const data = await response.json();
 102         if (data.access_token) {
 103             localStorage.setItem("access_token", data.access_token);
 104             showLoggedIn();
 105         }
 106     }
 107 
 108     function showLoggedIn() {
 109         document.getElementById("login-btn").style.display = "none";
 110         document.getElementById("logout-btn").style.display = "block";
 111         document.getElementById("auth-status").innerText = "Status: Logged In";
 112         document.getElementById("token-data").innerText = localStorage.getItem("access_token");
 113     }
 114 
 115     document.getElementById("logout-btn").onclick = () => {
 116         localStorage.clear();
 117         location.reload();
 118     };
 119 
 120     init();
 121 </script>
 122 </body>
 123 </html>

Azure Entra ID PKCE client - Gemini generated

  • Can you adapt the html to azure entra id endpoints ?

   1 <!DOCTYPE html>
   2 <html lang="en">
   3 <head>
   4     <title>Azure Entra ID PKCE Client</title>
   5     <style>
   6         body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 40px; background-color: #f3f2f1; }
   7         .container { max-width: 800px; margin: auto; background: white; padding: 30px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
   8         pre { background: #201f1e; color: #00eb00; padding: 15px; overflow-x: auto; border-radius: 4px; font-family: 'Cascadia Code', monospace; }
   9         button { padding: 12px 24px; cursor: pointer; background: #0078d4; color: white; border: none; font-weight: 600; }
  10         button:hover { background: #005a9e; }
  11         .hidden { display: none; }
  12     </style>
  13 </head>
  14 <body>
  15 
  16 <div class="container">
  17     <h2>Azure Entra ID Auth (Plain JS)</h2>
  18     <p id="auth-status">Checking session...</p>
  19     
  20     <button id="login-btn" class="hidden">Sign in with Microsoft</button>
  21     <button id="logout-btn" class="hidden">Sign Out</button>
  22     
  23     <h3>Token Response:</h3>
  24     <pre id="token-data">Not authenticated.</pre>
  25 </div>
  26 
  27 <script>
  28     // --- 1. CONFIGURE THESE THREE VALUES ---
  29     const TENANT_ID = "YOUR_TENANT_ID"; // Or "common" for multi-tenant
  30     const CLIENT_ID = "YOUR_APPLICATION_CLIENT_ID";
  31     const REDIRECT_URI = window.location.origin + window.location.pathname;
  32 
  33     const config = {
  34         authEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`,
  35         tokenEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`,
  36         // scope: "openid profile offline_access User.Read"
  37         scope: "openid profile User.Read" 
  38     };
  39 
  40     // --- PKCE CRYPTO UTILS ---
  41     async function generateCodeChallenge(codeVerifier) {
  42         const encoder = new TextEncoder();
  43         const data = encoder.encode(codeVerifier);
  44         const digest = await window.crypto.subtle.digest("SHA-256", data);
  45         return btoa(String.fromCharCode(...new Uint8Array(digest)))
  46             .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  47     }
  48 
  49     function generateRandomString(length) {
  50         const array = new Uint8Array(length);
  51         window.crypto.getRandomValues(array);
  52         const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  53         let result = '';
  54         for (let i = 0; i < length; i++) { result += chars.charAt(array[i] % chars.length); }
  55         return result;
  56     }
  57 
  58     // --- MAIN FLOW ---
  59     async function handleAuth() {
  60         const params = new URLSearchParams(window.location.search);
  61         const code = params.get("code");
  62 
  63         if (code) {
  64             await exchangeCodeForToken(code);
  65             window.history.replaceState({}, document.title, window.location.pathname);
  66         } else if (localStorage.getItem("azure_access_token")) {
  67             showLoggedIn();
  68         } else {
  69             document.getElementById("login-btn").classList.remove("hidden");
  70         }
  71     }
  72 
  73     document.getElementById("login-btn").onclick = async () => {
  74         const verifier = generateRandomString(64);
  75         localStorage.setItem("pkce_verifier", verifier);
  76         const challenge = await generateCodeChallenge(verifier);
  77 
  78         const authUrl = `${config.authEndpoint}?` + 
  79             `client_id=${CLIENT_ID}&` +
  80             `response_type=code&` +
  81             `redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` +
  82             `response_mode=query&` +
  83             `scope=${encodeURIComponent(config.scope)}&` +
  84             `code_challenge=${challenge}&` +
  85             `code_challenge_method=S256`;
  86 
  87         window.location.href = authUrl;
  88     };
  89 
  90     async function exchangeCodeForToken(code) {
  91         const verifier = localStorage.getItem("pkce_verifier");
  92         
  93         const response = await fetch(config.tokenEndpoint, {
  94             method: "POST",
  95             headers: { "Content-Type": "application/x-www-form-urlencoded" },
  96             body: new URLSearchParams({
  97                 client_id: CLIENT_ID,
  98                 grant_type: "authorization_code",
  99                 code: code,
 100                 redirect_uri: REDIRECT_URI,
 101                 code_verifier: verifier
 102                 // No client_secret needed for SPA/Public clients
 103             })
 104         });
 105 
 106         const data = await response.json();
 107         if (data.access_token) {
 108             localStorage.setItem("azure_access_token", data.access_token);
 109             localStorage.setItem("full_response", JSON.stringify(data, null, 2));
 110             showLoggedIn();
 111         } else {
 112             alert("Error: " + data.error_description);
 113         }
 114     }
 115 
 116     function showLoggedIn() {
 117         document.getElementById("login-btn").classList.add("hidden");
 118         document.getElementById("logout-btn").classList.remove("hidden");
 119         document.getElementById("auth-status").innerText = "Status: Signed in to Azure Entra ID";
 120         document.getElementById("token-data").innerText = localStorage.getItem("full_response");
 121     }
 122 
 123     document.getElementById("logout-btn").onclick = () => {
 124         localStorage.clear();
 125         window.location.reload();
 126     };
 127 
 128     handleAuth();
 129 </script>
 130 </body>
 131 </html>
  • MoinMoin Powered
  • Python Powered
  • GPL licensed
  • Valid HTML 4.01