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

Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

  • 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