Lab - Using htmlYac as OIDC Client
htmlYac allows flexible scripting by flowing data from one response to another request. We'll use this to try connecting with OIDC Provider
Preparation
- Simple OIDC Provider
- htmlYac installed
Launching OIDC Provider
Change the OIDC provider a little:
import express from 'express';
import Provider from 'oidc-provider';
const PORT = 3100
const app = express();
const configuration = {
clients: [{
client_id: 'oidccli_id',
client_secret: 'oidccli_secret',
grant_types: ['authorization_code'],
redirect_uris: ['http://localhost:3000/callback'],
post_logout_redirect_uris: ['http://localhost:3000/'],
response_types: ['code'],
}],
pkce: {
required: () => false,
},
scopes: [
'email', 'profile',
],
claims: {
email: ['email'],
profile: ['first_name'],
},
async findAccount(ctx, id) {
return {
accountId: id,
async claims(use, scope) {
console.log("FIND", id, "USE", use, "SCOPE", scope);
return { sub: id, email: `${id}@example.com`, first_name: id };
},
};
},
};
const oidc = new Provider(`http://localhost:${PORT}`, configuration);
app.use('/oidc', oidc.callback());
app.listen(PORT, function () {
console.log(`OIDC is listening on http://localhost:${PORT}`);
});
- the port is changed from 3000 to 3100 (httpYac uses 3000 for its temp server)
- no PKCE for simplicity
$ node oidcsrv
OIDC is listening on http://localhost:3100
From VS Code, in the oidc.http file, try the request:
@host=http://localhost:3100
###
GET /oidc/.well-known/openid-configuration
The configuration is displayed:
{
...
"authorization_endpoint": "http://localhost:3100/oidc/auth",
"end_session_endpoint": "http://localhost:3100/oidc/session/end",
"grant_types_supported": [
"implicit",
"authorization_code"
],
"scopes_supported": [
"email",
"profile",
"openid"
],
"userinfo_endpoint": "http://localhost:3100/oidc/me",
...
}
Start the process
GET /oidc
Authorization: oauth2 code local
httpYac will spin up a server asking you to login (to the OIDC provider) and confirm. If successful, we can send the next request:
GET /oidc/me
Authorization: oauth2 code local
The result may look like:
{
"sub": "john",
"email": "john@example.com",
"first_name": "john"
}
Logging out
In VS Code we can open the command palette and find the command "httpYac: Logout User Sessions", but resending the requests above will still succeed since we haven't logged out the OIDC provider.
Since we know the end_session_endpoint
from the earlier response, to logout from the OIDC provider, we can issue:
GET http://localhost:3100/oidc/session/end
Authorization: oauth2 code local
We'll see an HTML response, in which a form contains something like:
<form method="post" action="http://localhost:3100/oidc/session/end/confirm">
<input type="hidden" name="xsrf" value="some_random_key" />
<input type="hidden" name="logout" value="yes" />
...
</form>
We can post the form back to log out by construction another POST request with the required xsrf
and logout
value. But without doing it manually, we can use httpYac scripting:
{{
let re = /name="xsrf" value="([\w\d]+)"/
let m = response.body.match(re)
exports.xsrf = m[1];
}}
POST http://localhost:3100/oidc/session/end/confirm
Authorization: oauth2 code local
Content-Type: application/x-www-form-urlencoded
logout=yes&xsrf={{ xsrf }}
If it succeeds, we will get some HTML response like
...
<div class="container">
<h1>Sign-out Success</h1>
<p>Your sign-out was successful.</p>
</div>
Still, to make sure we are logged out, you may have to run httpYacc: Logout User Sessions
in VS Code, and to clear the site data for httpYacc's server (http://localhost:3000
).
Using Requests Directly
To look closely what exchanges are made, add logging to the OIDC provider:
import bodyParser from 'body-parser';
...
app.use(bodyParser());
app.use((req, res, next) => {
console.log('HTTP', req.method, req.url);
console.log(' headers:', req.headers);
req.body && console.log(' body:', JSON.stringify(req.body));
});
...
To be continued...