Commit ce54f4fc authored by jackie / Andrea Ida Malkah Klaura's avatar jackie / Andrea Ida Malkah Klaura
Browse files

add javascript client stub with implicit flow

parent 7842968e
......@@ -58,6 +58,24 @@ Make sure to have the config right, that can be imported from _config.py_
in the `parameters` dictionary, depending on what flow you choose (e.g.
`parameters["code"] = "it_token token"` for an implicit flow).
### Javascript
For the JS client the approach is a bit different, as the most probable
usage will be on some webspace and therefore we can use the browser for
all redirecting. While the implementation of the flow stages is therefore
much simpler, we need a separate callback page.
Also if you test this out locally as a _file://_ in the browser, the automatic
redirect to the callback page after successful authentication will not work.
In that case you have to use the developer console and check the last POST
request in the network monitor and use the `Location:` header manually.
If you put this on any local or remote web server so that you can access it
through HTTP, this should work all fine.
Take a look at the [javascript/index.html](javascript/index.html) page for
a start.
TODO: a bit more docs here
## Planned and upcoming features
......@@ -69,6 +87,7 @@ in the `parameters` dictionary, depending on what flow you choose (e.g.
- done for python, still todo for bash
- Test (and provide sensible error message) for
- invalid flow type
- invalid redirect uris
- invalid credentials
- For the bash client stub:
- add user-agent header and make it configurable
......
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Callback page for OIDC JS Client Stub for AuRa</title>
<style>
#debuggingInfo {
border: 1px dotted black;
padding: 0.5em;
margin-bottom: 1em;
word-break: break-all;
}
</style>
</head>
<body>
<h1>OIDC callback page</h1>
Debugging info:
<div id="debuggingInfo"></div>
<button onclick="finish_flow()">Finish the OIDC flow and return to index.html</button>
<script>
// extract the relevant query parameters with the token info
let params = new URLSearchParams(String(location).split('#', 2)[1])
let id_token_contents = null
if (params.has("id_token")) {
id_token_contents = JSON.parse(
atob(params.get("id_token").split('.')[1])
)
}
// store it in localStorage, so that index.html can access it later
localStorage.user = JSON.stringify({
access_token: params.get("access_token"),
id_token: params.get("id_token"),
id_token_contents: id_token_contents,
code: params.get("code"),
})
let div = document.getElementById("debuggingInfo")
div.innerText = localStorage.user
// in a production setup the browser should be forwarded automatically
// but here we want wait for the user to press the button, except
// debugging is disabled
const finish_flow = function () {
location.assign("index.html")
}
if (localStorage.oidcCallbackDebugging === undefined) {
finish_flow()
}
</script>
</body>
</html>
let cfg = {
username: "jackie",
password: "ladidahaxaba",
scope: "openid profile email username aura_shows",
base_url: "http://localhost:8000",
authorize_endpoint: "/openid/authorize",
userinfo_endpoint: "/openid/userinfo",
token_endpoint: "/openid/token",
client_id: "365085",
client_secret: "ac74f3975ef2994e12cdee4297e14b91a1d222f16a40f17a1071c8f9",
redirect_uri: "file:///home/jackie/scratch/dev/ORANGE94.0/autoradio/oidc-client-stubs/javascript/callback.html",
user_agent: "AURA Javscript Client Stub 0.1",
tank_base: "http://localhost:8040",
tank_session_endpoint: "/auth/session",
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>OpenID Connect Client Stub for AuRa</title>
<style>
#notLoggedIn, #authCode, #loggedIn {
display: none;
}
#loggedIn>div {
border: 1px dotted black;
padding: 0.5em;
margin: 1em 0;
word-break: break-all;
}
#loggedIn>span, #loggedIn>div>span {
font-family: monospace;
font-weight: bold;
}
#loggedIn>div>span#expired_notice {
display: none;
color: #f00;
font-family: inherit;
}
</style>
</head>
<body>
<div>
<h1>Debugging Settings</h1>
Output debugging info to console and wait for manual forward at the
callback page: <input type="checkbox" id="checkboxDebug">
</div>
<hr>
<div>
<h1>User Info</h1>
<div id="notLoggedIn">
You are not logged in yet. Initiatie a flow to log in.
</div>
<div id="authCode">
You retrieved the following authorization code: <span id="access_code"></span><br>
<button onclick="alert('todo!')">Exchange against access token</button>
</div>
<div id="loggedIn">
Your user credentials are (retrieved <span id="token_retrieved"></span>):
<div>
Access token: <span id="access_token"></span><br>
ID Token: <span id="id_token"></span> <small>
(<a href="https://jwt.io/" target="_blank">jwt.io</a>)</small><br>
User ID: <span id="user_id"></span><br>
e-Mail: <span id="user_email"></span><br>
Token expires: <span id="token_expires"></span>
<span id="expired_notice">This token has already expired!</span><br>
</div>
</div>
</div>
<hr>
<div>
<h1>Available flows for logging in</h1>
<button onclick="start_implicit()">Start implicit flow</button>
</div>
<script src="config.js"></script>
<script src="steering/flow_stages.js"></script>
<script src="steering/implicit.js"></script>
<script src="main.js"></script>
</body>
</html>
// some of the UI elements we need for user interaction
const checkboxDebug = document.getElementById("checkboxDebug")
const divNotLoggedIn = document.getElementById("notLoggedIn")
const divAuthCode = document.getElementById("authCode")
const divLoggedIn = document.getElementById("loggedIn")
const spanAccessCode = document.getElementById("access_code")
const spanAccessToken = document.getElementById("access_token")
const spanIDToken = document.getElementById("id_token")
const spanUserID = document.getElementById("user_id")
const spanUserEmail = document.getElementById("user_email")
const spanTokenExpires = document.getElementById("token_expires")
const spanTokenRetrieved = document.getElementById("token_retrieved")
const spanExpiredNotice = document.getElementById("expired_notice")
// when the debug output checkbox is clicked
checkboxDebug.addEventListener("click", function () {
if (checkboxDebug.checked) {
cfg.verbosity = 1
localStorage.oidcCallbackDebugging = true
} else {
cfg.verbosity = 0
localStorage.removeItem("oidcCallbackDebugging")
}
})
// check the debug output checkbox, if it was checked the last time
if (localStorage.oidcCallbackDebugging) {
checkboxDebug.checked = true
}
// check the localStorage if we already have a user object with a valid token
let userObject
if (localStorage.user === undefined) {
divNotLoggedIn.style.display = "block"
} else {
userObject = JSON.parse(localStorage.user)
console.log(userObject)
// authorization code flow only returns an access code
if (userObject.code) {
divAuthCode.style.display = "block"
spanAccessCode.innerText = userObject.code
} else {
divLoggedIn.style.display = "block"
spanAccessToken.innerText = userObject.access_token
spanIDToken.innerText = userObject.id_token
spanUserID.innerText = userObject.id_token_contents.sub
spanUserEmail.innerText = userObject.id_token_contents.email
spanTokenRetrieved.innerText = String(new Date(userObject.id_token_contents.iat*1000))
spanTokenExpires.innerText = String(new Date(userObject.id_token_contents.exp*1000))
if (new Date(userObject.id_token_contents.exp*1000) < new Date()) {
spanExpiredNotice.style.display = "inline"
}
}
}
// this is how we actually start an implicit flow, when the user hits the button
const start_implicit = function () {
let parameters = {
response_type: "id_token token"
}
implicit.get_token(cfg, parameters)
}
const flow_stages = {
initiate_flow (cfg, parameters) {
let url = cfg.base_url + cfg.authorize_endpoint + "?" +
"client_id=" + cfg.client_id + "&" +
"scope=" + cfg.scope + "&" +
"redirect_uri=" + cfg.redirect_uri + "&" +
"response_type=" + parameters.response_type + "&" +
"state=" + parameters.state + "&" +
"nonce=" + parameters.nonce + "&"
console.log(url)
location.assign(url)
},
}
const implicit = {
get_token (cfg, parameters) {
parameters.state = "12345"
parameters.nonce = "12345"
parameters.location = flow_stages.initiate_flow(cfg, parameters)
//parameters["callback"] = flow_stages.handle_login_form(cfg, parameters)
// return the token information extracted from the callback
//return flow_stages.get_token_from_callback(cfg, parameters["callback"])
},
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment