This is the Vanilla JavaScript client side code to accompany JWT on .NET Core 2.2 and Little Else. I have the sourcecode on my Github account as a branch of the other project.
Logging in (as far as it goes)
To log in, I need to make a request to my services with the data gathered from my “login” form. The request is a POST and the in sent to the server as JSON (“content-Type:application/json” in the head. When I get a response back from the server, XMLHttpRequest calls getJwtProcessResponse():
function getJwt() { showResults("working ...."); // Assemble data to log in, should match schema of MakeTokenViewModel const userName = document.getElementById("txtUserName").value; const role = document.getElementById("txtRole").value; const id = document.getElementById("txtId").value; const fail = document.getElementById("chkFail").checked; const message = `{"UserName":"${userName}","Role": "${role}","Id": ${id},"Fail":${fail}}`; // Make the request const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = getJwtProcessResponse; xhttp.open("Post", `${BASE_URL}jwt/maketoken`, true); xhttp.setRequestHeader("content-Type", "application/json"); xhttp.send(message); }
When I get a response back and it’s ready: if the status is 200, login succeeded, and I store the JWT in Local Storage with writeJwt() (since I’m not sending anything other than the JWT, I don’t have to parse it out of this.responseText). Otherwise, alert the user of the failure.
// Get and process the request function getJwtProcessResponse() { if (this.readyState == 4) { if (this.status == 200) { writeJwt(this.responseText); showResults("token written to localStorage"); } else { alert(`${this.status}\n ${this.responseText}`); showResults("Failed!"); } } }
Making a call to the Service
Here I am going to make a simple GET request with the JWT in Local Storage (if available)
// Make a call to the end point specified by urlExtension // (each endpoint has a different permission, for demo purpose) function makeCall(urlExtension) { showResults("working ...."); const jwt = readJwt(); const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = makeCallProcessResponse; xhttp.open("Get", `${BASE_URL}values${urlExtension}`, true); // We want to test with the user not logged in if (!!jwt) { xhttp.setRequestHeader("Authorization", ` Bearer ${jwt}`); } xhttp.send(); }
When I get a response back and it’s ready: if the status is 200, I am authorized to and call showResults() to show what I got back. Otherwise the user is alerted about the failure
function makeCallProcessResponse() { if (this.readyState == 4) { if (this.status == 200) { showResults(this.responseText); } else { alert(`${this.status}\n ${this.responseText}`); showResults("Failed!"); } } }
Logging out
You really don’t log off JWT, they expire. To simulate logging out of a site, you make Local Storage forget:
function deleteJwt() { clearJwt(); }
Reading JWT “payload”
You can embed data in middle part of the JWT (the Payload) as unencrypted Base64 encoded JSON (Yes, I stole it from StackOverflow).
//see https://stackoverflow.com/a/38552302/3819 function parseJwt(token) { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); return JSON.parse(window.atob(base64)); }
The localStorage functions
These are the functions that actually read and write the JWT to localStorage:
function writeJwt(jwt) { if (typeof Storage !== "undefined") { localStorage.setItem("jwt", jwt); } else { showResults("Sorry, your browser does not support Web Storage...") } } function readJwt() { if (typeof Storage !== "undefined") { return localStorage.getItem("jwt"); } else { showResults("Sorry, your browser does not support Web Storage..."); } return ""; } function clearJwt() { if (typeof Storage !== "undefined") { localStorage.removeItem("jwt"); } else { showResults("Sorry, your browser does not support Web Storage..."); } }
The Whole Page
And here is the whole page (JS, CSS and HTML all in one):
<!DOCTYPE html> <html> <head> <style> body { margin: 25px; } label { width: 80px; display: inline-block; margin: 2px; } input { margin: 2px; } button { width: 150px; margin: 2px; } #result { width: 100%; height: 150px; } .grid-container { width: 100%; display: grid; grid-gap: 10px; grid-template-columns: 1fr 1fr 1fr; } .grid-container { display: inline-grid; } </style> </head> <body> <h2>JWT Client</h2> <div id="theGrid"class="grid-container"> <div id="data"class="grid-item" > <h3>Data for JWT</h3> <p>This is the data used to create the JWT. The Server recognizes 2 roles: "admin" and "super".</p> <p>(We can simulate a log in failure by checking "Fail")</p> <label for="txtUserName">User Name </label><input type="text" id="txtUserName" value="johns" /><br /> <label for="txtRole">Role</label><input type="text" id="txtRole" value="admin" /><br /> <label for="txtId">Id</label><input type="number" id="txtId" value="42" /><br /> <label for="chkFail">Fail</label><input type="checkbox" id="chkFail" value="false" /><br /> </div> <div id="access" class="grid-item"> <h3>Make JWT</h3> <p>Here we get the JWT from the "server", store and retrieve the JWT.</p> <p>The JWT is stored in Local Storage.</p> <button type="button" onclick="getJwt()">Get Token</button><br /> <button type="button" onclick="showJwt()">Show Token</button><br /> <button type="button" onclick="decode()">Decode</button><br /> <button type="button" onclick="deleteJwt()">Clear Token</button><br /> </div> <div id="use" class="grid-item"> <h3>Use JWT</h3> <p>Here we use the JWT we stored in Local Storage and make calls to various endpoints which have different permissions on the server</p> <p>The Server decodes the claims in the JWT and returns them as JSON object</p> <button type="button" onclick="makeCall('')">Call "/"</button><br /> <button type="button" onclick="makeCall('/admin')">Call "/admin"</button><br /> <button type="button" onclick="makeCall('/super')">Call "/super"</button><br /> <button type="button" onclick="makeCall('/either')">Call "/either"</button><br /> <button type="button" onclick="makeCall('/open')">Call "/open"</button><br /> </div> </div> <div id="results"> <label for="result">Results</label><br /> <textarea id="result"></textarea> </div> <script> const BASE_URL = "/api/"; function getJwt() { showResults("working ...."); // Assemble data to log in, should match schema of MakeTokenViewModel const userName = document.getElementById("txtUserName").value; const role = document.getElementById("txtRole").value; const id = document.getElementById("txtId").value; const fail = document.getElementById("chkFail").checked; const message = `{"UserName":"${userName}","Role": "${role}","Id": ${id},"Fail":${fail}}`; // Make the request const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = getJwtProcessResponse; xhttp.open("Post", `${BASE_URL}jwt/maketoken`, true); xhttp.setRequestHeader("content-Type", "application/json"); xhttp.send(message); } // Get and process the request function getJwtProcessResponse() { if (this.readyState == 4) { if (this.status == 200) { writeJwt(this.responseText); showResults("token written to localStorage"); } else { alert(`${this.status}\n ${this.responseText}`); showResults("Failed!"); } } } // Make a call to the end point specified by urlExtension // (each endpoint has a different permission, for demo purpose) function makeCall(urlExtension) { showResults("working ...."); const jwt = readJwt(); const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = makeCallProcessResponse; xhttp.open("Get", `${BASE_URL}values${urlExtension}`, true); // We want to test with the user not logged in if (!!jwt) { xhttp.setRequestHeader("Authorization", ` Bearer ${jwt}`); } xhttp.send(); } function makeCallProcessResponse() { if (this.readyState == 4) { if (this.status == 200) { showResults(this.responseText); } else { alert(`${this.status}\n ${this.responseText}`); showResults("Failed!"); } } } function showJwt() { const jwt = readJwt(); if (jwt === null) { showResults("no token to display"); return; } showResults(jwt); } function deleteJwt() { clearJwt(); } function writeJwt(jwt) { if (typeof Storage !== "undefined") { localStorage.setItem("jwt", jwt); } else { showResults("Sorry, your browser does not support Web Storage...") } } function readJwt() { if (typeof Storage !== "undefined") { return localStorage.getItem("jwt"); } else { showResults("Sorry, your browser does not support Web Storage..."); } return ""; } function clearJwt() { if (typeof Storage !== "undefined") { localStorage.removeItem("jwt"); } else { showResults("Sorry, your browser does not support Web Storage..."); } } function decode() { const jwt = readJwt(); if (jwt == null) { showResults("no token to decode"); return; } const parsed = parseJwt(jwt); showResults(JSON.stringify(parsed, null, 2)); } //see https://stackoverflow.com/a/38552302/3819 function parseJwt(token) { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); return JSON.parse(window.atob(base64)); }; function showResults(results) { document.getElementById("result").value = results; } </script> </body> </html>
No comments:
Post a Comment