Make state optional and do not use it by default in code, pkce and webauthn flows

This commit is contained in:
Dusan Jakub
2023-10-11 17:39:37 +02:00
parent 0016f30120
commit 0c6c41f469
10 changed files with 79 additions and 48 deletions

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token
@@ -56,7 +56,7 @@
<div class="card horizontal"> <div class="card horizontal">
<div class="card-stacked"> <div class="card-stacked">
<div class="card-content"> <div class="card-content">
<h6>2. Verify the state parameter</h6> <h6>2. Parse response</h6>
<p> <p>
You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter. You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter.
In the URL you can notice, that there are addtional query parameters: In the URL you can notice, that there are addtional query parameters:
@@ -64,7 +64,7 @@
<pre class="code-block"><code id="queryParams"></code></pre> <pre class="code-block"><code id="queryParams"></code></pre>
<p>Let's break it down...</p> <p>Let's break it down...</p>
<ul class="collection"> <ul class="collection">
<li class="collection-item"> <li class="collection-item has-state">
<p><b><span class="emphasis">state</span>=<span id="state"></span></b></p> <p><b><span class="emphasis">state</span>=<span id="state"></span></b></p>
<p> <p>
The state parameter is an opaque value used by the client to maintain state between the request and the callback. The state parameter is an opaque value used by the client to maintain state between the request and the callback.
@@ -81,10 +81,10 @@
</p> </p>
</li> </li>
</ul> </ul>
<p> <p class="has-state">
Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to the one we received back (<b><span id="received-state"></span></b>)? Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to the one we received back (<b><span id="received-state"></span></b>)?
</p> </p>
<div class="row flow-submit-container"> <div class="row flow-submit-container has-state">
<div class="col m6 s12" style="margin-bottom: 5px;"> <div class="col m6 s12" style="margin-bottom: 5px;">
<a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a> <a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a>
</div> </div>
@@ -92,6 +92,10 @@
<a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a> <a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a>
</div> </div>
</div> </div>
<div class="row flow-submit-container no-state">
<a id="get-token-btn" class="waves-effect waves-light btn full-width" onClick="proceedToNextStep()">Continue</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -111,9 +115,9 @@
const state = urlParams.get("state"); const state = urlParams.get("state");
const sentState = getCookie("state"); const sentState = getCookie("state");
if (!code || !state || !sentState) { // if (!code || !state || !sentState) {
window.location = "/flow/expired"; // window.location = "/flow/expired";
} // }
$("#queryParams").text(window.location.search) $("#queryParams").text(window.location.search)
$("#state").text(state); $("#state").text(state);
@@ -121,6 +125,9 @@
$("#received-state").text(state); $("#received-state").text(state);
$("#code").text(code); $("#code").text(code);
$(".has-state").toggle(!!state);
$(".no-state").toggle(!state);
function proceedToNextStep() { function proceedToNextStep() {
window.location.href = "/flow/code-3" + window.location.search; window.location.href = "/flow/code-3" + window.location.search;
} }

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token
@@ -100,7 +100,7 @@
token. In this case, we are requesting access to <b>photos</b> and <b>files</b>. token. In this case, we are requesting access to <b>photos</b> and <b>files</b>.
</p> </p>
</li> </li>
<li class="collection-item"> <!-- <li class="collection-item">
<p><b><span class="emphasis">state</span>=<span id="state"></span></b></p> <p><b><span class="emphasis">state</span>=<span id="state"></span></b></p>
<p> <p>
State parameter. This is an <b>optional parameter</b> that the client can use to maintain State parameter. This is an <b>optional parameter</b> that the client can use to maintain
@@ -108,7 +108,7 @@
redirecting the user back to the client, allowing the client to verify that the response is coming from the redirecting the user back to the client, allowing the client to verify that the response is coming from the
server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>). server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>).
</p> </p>
</li> </li> -->
</ul> </ul>
<p> <p>
All that we now need to do is click the button below and login with our credentials. All that we now need to do is click the button below and login with our credentials.
@@ -144,7 +144,8 @@
+ "&" + "client_id=" + getClientId() + "&" + "client_id=" + getClientId()
+ "&" + "redirect_uri=" + redirectUri + "&" + "redirect_uri=" + redirectUri
+ "&" + "scope=" + scope + "&" + "scope=" + scope
+ "&" + "state=" + state; //+ "&" + "state=" + state
;
} }
function fillExample() { function fillExample() {
@@ -153,7 +154,8 @@
+ "&client_id=" + getClientId() + "\n" + "&client_id=" + getClientId() + "\n"
+ "&redirect_uri=" + redirectUri + "\n" + "&redirect_uri=" + redirectUri + "\n"
+ "&scope=" + scope + "\n" + "&scope=" + scope + "\n"
+ "&state=" + state; //+ "&state=" + state
;
$("#requestUriExample").text(requestExample); $("#requestUriExample").text(requestExample);
$("#baseUrl").text(baseUrl); $("#baseUrl").text(baseUrl);
@@ -172,7 +174,7 @@
const responseType = "code"; const responseType = "code";
const redirectUri = getRedirectUri(); const redirectUri = getRedirectUri();
const scope = "photos%20files"; const scope = "photos files";
const state = generateSessionState(); const state = generateSessionState();
setCookie("state", state, 5); setCookie("state", state, 5);

View File

@@ -49,7 +49,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
After the user is redirected back to the client, verify the state The user is redirected back to the client
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
Exchange the authorization code and code verifier for an access token Exchange the authorization code and code verifier for an access token
@@ -103,7 +103,7 @@
token. In this case, we are requesting access to <b>photos</b> and <b>files</b>. token. In this case, we are requesting access to <b>photos</b> and <b>files</b>.
</p> </p>
</li> </li>
<li class="collection-item"> <!-- <li class="collection-item">
<p><b><span class="emphasis">state</span>=<span id="state"></span></b></p> <p><b><span class="emphasis">state</span>=<span id="state"></span></b></p>
<p> <p>
State parameter. This is an <b>optional parameter</b> that the client can use to maintain State parameter. This is an <b>optional parameter</b> that the client can use to maintain
@@ -111,7 +111,7 @@
user back to the client, allowing the client to verify that the response is coming from the user back to the client, allowing the client to verify that the response is coming from the
server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>). server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>).
</p> </p>
</li> </li> -->
<li class="collection-item"> <li class="collection-item">
<p><b><span class="emphasis">code_challenge</span>=<span id="codeChallenge"></span></b></p> <p><b><span class="emphasis">code_challenge</span>=<span id="codeChallenge"></span></b></p>
<p> <p>
@@ -159,7 +159,7 @@
+ "&" + "client_id=" + getClientId() + "&" + "client_id=" + getClientId()
+ "&" + "redirect_uri=" + redirectUri + "&" + "redirect_uri=" + redirectUri
+ "&" + "scope=" + scope + "&" + "scope=" + scope
+ "&" + "state=" + state //+ "&" + "state=" + state
+ "&" + "code_challenge=" + codeChallenge + "&" + "code_challenge=" + codeChallenge
+ "&" + "code_challenge_method=S256"; + "&" + "code_challenge_method=S256";
} }
@@ -170,7 +170,7 @@
+ "&client_id=" + getClientId() + "\n" + "&client_id=" + getClientId() + "\n"
+ "&redirect_uri=" + redirectUri + "\n" + "&redirect_uri=" + redirectUri + "\n"
+ "&scope=" + scope + "\n" + "&scope=" + scope + "\n"
+ "&state=" + state + "\n" //+ "&state=" + state + "\n"
+ "&code_challenge=" + codeChallenge + "\n" + "&code_challenge=" + codeChallenge + "\n"
+ "&code_challenge_method=S256"; + "&code_challenge_method=S256";
@@ -191,7 +191,7 @@
const responseType = "code"; const responseType = "code";
const redirectUri = getRedirectUri(); const redirectUri = getRedirectUri();
const scope = "photos%20files"; const scope = "photos files";
const state = generateSessionState(); const state = generateSessionState();
const codeChallenge = getCookie("code_challenge"); const codeChallenge = getCookie("code_challenge");

View File

@@ -51,7 +51,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
After the user is redirected back to the client, verify the state The user is redirected back to the client
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
Exchange the authorization code and code verifier for an access token Exchange the authorization code and code verifier for an access token
@@ -63,14 +63,14 @@
<div class="card horizontal"> <div class="card horizontal">
<div class="card-stacked"> <div class="card-stacked">
<div class="card-content"> <div class="card-content">
<h6>3. Verify the state parameter</h6> <h6>3. Parse response</h6>
<p> <p>
You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter. In the URL you can notice, that there are addtional query parameters: You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter. In the URL you can notice, that there are addtional query parameters:
</p> </p>
<pre class="code-block"><code id="queryParams"></code></pre> <pre class="code-block"><code id="queryParams"></code></pre>
<p>Let's break it down...</p> <p>Let's break it down...</p>
<ul class="collection"> <ul class="collection">
<li class="collection-item"> <li class="collection-item has-state">
<p><b><span class="emphasis">state</span>=<span id="state"></span></b></p> <p><b><span class="emphasis">state</span>=<span id="state"></span></b></p>
<p> <p>
The state parameter is an opaque value used by the client to maintain state between the request and the callback. The state parameter is an opaque value used by the client to maintain state between the request and the callback.
@@ -93,11 +93,11 @@
</p> </p>
</li> </li>
</ul> </ul>
<p> <p class="has-state">
Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to
the one we received back (<b><span id="received-state"></span></b>)? the one we received back (<b><span id="received-state"></span></b>)?
</p> </p>
<div class="row flow-submit-container"> <div class="row flow-submit-container has-state">
<div class="col m6 s12" style="margin-bottom: 5px;"> <div class="col m6 s12" style="margin-bottom: 5px;">
<a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a> <a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a>
</div> </div>
@@ -105,6 +105,10 @@
<a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a> <a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a>
</div> </div>
</div> </div>
<div class="row flow-submit-container no-state">
<a id="get-token-btn" class="waves-effect waves-light btn full-width" onClick="proceedToNextStep()">Continue</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -124,15 +128,18 @@
const state = urlParams.get('state'); const state = urlParams.get('state');
const sentState = getCookie("pkce-state"); const sentState = getCookie("pkce-state");
if (!code || !state || !sentState) { // if (!code || !state || !sentState) {
window.location = "/flow/expired"; // window.location = "/flow/expired";
} // }
$("#queryParams").text(window.location.search) $("#queryParams").text(window.location.search)
$("#state").text(state); $("#state").text(state);
$("#sent-state").text(sentState); $("#sent-state").text(sentState);
$("#received-state").text(state); $("#received-state").text(state);
$("#code").text(code); $("#code").text(code);
$(".has-state").toggle(!!state);
$(".no-state").toggle(!state);
function proceedToNextStep() { function proceedToNextStep() {
window.location.href = "/flow/pkce-4" + window.location.search; window.location.href = "/flow/pkce-4" + window.location.search;

View File

@@ -51,7 +51,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
After the user is redirected back to the client, verify the state The user is redirected back to the client
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
Exchange the authorization code and code verifier for an access token Exchange the authorization code and code verifier for an access token

View File

@@ -50,7 +50,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
After the user is redirected back to the client, verify the state The user is redirected back to the client
</div> </div>
<div class="col s3 circle-text"> <div class="col s3 circle-text">
Exchange the authorization code and code verifier for an access token Exchange the authorization code and code verifier for an access token

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token
@@ -56,7 +56,7 @@
<div class="card horizontal"> <div class="card horizontal">
<div class="card-stacked"> <div class="card-stacked">
<div class="card-content"> <div class="card-content">
<h6>2. Verify the state parameter</h6> <h6>2. Parse response</h6>
<p> <p>
You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter. In the URL You have now been redirected back to the application, to the page that was specified in the <b>redirect-url</b> parameter. In the URL
you can notice, that there are addtional query parameters: you can notice, that there are addtional query parameters:
@@ -64,7 +64,7 @@
<pre class="code-block"><code id="queryParams"></code></pre> <pre class="code-block"><code id="queryParams"></code></pre>
<p>Let's break it down...</p> <p>Let's break it down...</p>
<ul class="collection"> <ul class="collection">
<li class="collection-item"> <li class="collection-item has-state">
<p><b><span class="emphasis">state</span>=<span id="state"></span></b></p> <p><b><span class="emphasis">state</span>=<span id="state"></span></b></p>
<p> <p>
The state parameter is an opaque value used by the client to maintain state between the request and the callback. The state parameter is an opaque value used by the client to maintain state between the request and the callback.
@@ -82,10 +82,10 @@
</p> </p>
</li> </li>
</ul> </ul>
<p> <p class="has-state">
Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to the one we received back (<b><span id="received-state"></span></b>)? Now we have everything necessary to obtain token for the user. But is the state we have sent (<b><span id="sent-state"></span></b>) equivalent to the one we received back (<b><span id="received-state"></span></b>)?
</p> </p>
<div class="row flow-submit-container"> <div class="row flow-submit-container has-state">
<div class="col m6 s12" style="margin-bottom: 5px;"> <div class="col m6 s12" style="margin-bottom: 5px;">
<a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a> <a class="waves-effect waves-light btn btn-success full-width" onclick="proceedToNextStep()">States are matching</a>
</div> </div>
@@ -93,6 +93,10 @@
<a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a> <a class="waves-effect waves-light btn btn-error full-width" href="/flow/code">States are not matching</a>
</div> </div>
</div> </div>
<div class="row flow-submit-container no-state">
<a id="get-token-btn" class="waves-effect waves-light btn full-width" onClick="proceedToNextStep()">Continue</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -112,9 +116,9 @@
const state = urlParams.get("state"); const state = urlParams.get("state");
const sentState = getCookie("webauth-state"); const sentState = getCookie("webauth-state");
if (!code || !state || !sentState) { // if (!code || !state || !sentState) {
window.location = "/flow/expired"; // window.location = "/flow/expired";
} // }
$("#queryParams").text(window.location.search) $("#queryParams").text(window.location.search)
$("#state").text(state); $("#state").text(state);
@@ -122,6 +126,9 @@
$("#received-state").text(state); $("#received-state").text(state);
$("#code").text(code); $("#code").text(code);
$(".has-state").toggle(!!state);
$(".no-state").toggle(!state);
function proceedToNextStep() { function proceedToNextStep() {
window.location.href = "/flow/webauthn-3" + window.location.search; window.location.href = "/flow/webauthn-3" + window.location.search;
} }

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token

View File

@@ -44,7 +44,7 @@
Build the authorization URL and redirect the user to the authorization server Build the authorization URL and redirect the user to the authorization server
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
After the user is redirected back to the client, verify that the state matches The user is redirected back to the client
</div> </div>
<div class="col s4 circle-text"> <div class="col s4 circle-text">
Exchange the authorization code for an access token Exchange the authorization code for an access token
@@ -58,11 +58,17 @@
<div class="card-content"> <div class="card-content">
<h6>1. Build the Authorization URL</h6> <h6>1. Build the Authorization URL</h6>
<p> <p>
In order to initiate the <b>WebAuthn</b>, we proceed the same way as we would if we would be using <b>Authorization Code Flow</b> we need to build the WebAuthn is not an OAuth grant, but rather a new browser API to facilitate passwordless login.
authorization URL and redirect the user to the authorization server. All the passwordless logic is handled completely by the authorization server. The URL is constructed as follows: </p>
<p>It can be used with any grant that renders a login form,
such as <a href="/flow/code">Authorization Code Flow</a> or <a href="/flow/dag">Device Authorization Grant</a>, or even outside OAuth for your application's custom login.
</p>
<p>
For this demostration, we'll use an <a href="/flow/code">Authorization Code Flow</a> with a custom URL. In practice, if and when Webauthn is used instead of passwords passwords depends on the Authority Server settings.
</p>
</p> </p>
<pre class="code-block"><code id="requestUriExample"></code></pre> <pre class="code-block"><code id="requestUriExample"></code></pre>
<p>Let's break it down...</p> <!-- <p>Let's break it down...</p>
<ul class="collection"> <ul class="collection">
<li class="collection-item"> <li class="collection-item">
<p><b><span id="baseUrl"></span></b></p> <p><b><span id="baseUrl"></span></b></p>
@@ -111,7 +117,7 @@
server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>). server and not a malicious third party (<a href="https://owasp.org/www-community/attacks/csrf" target="_blank">CSRF attack</a>).
</p> </p>
</li> </li>
</ul> </ul> -->
<p> <p>
All that we now need to do is click the button below and perform the <b>Web Authentication Flow</b> on the server. After your account will be successfully verified, you will be redirected back to this playground, to the URL we have specified in the <b>redirect_uri</b> query parameter of the request. All that we now need to do is click the button below and perform the <b>Web Authentication Flow</b> on the server. After your account will be successfully verified, you will be redirected back to this playground, to the URL we have specified in the <b>redirect_uri</b> query parameter of the request.
</p> </p>
@@ -143,7 +149,8 @@
+ "&" + "client_id=" + getClientId() + "&" + "client_id=" + getClientId()
+ "&" + "redirect_uri=" + redirectUri + "&" + "redirect_uri=" + redirectUri
+ "&" + "scope=" + scope + "&" + "scope=" + scope
+ "&" + "state=" + state; //+ "&" + "state=" + state
;
} }
function fillExample() { function fillExample() {
@@ -152,7 +159,8 @@
+ "&client_id=" + getClientId() + "\n" + "&client_id=" + getClientId() + "\n"
+ "&redirect_uri=" + redirectUri + "\n" + "&redirect_uri=" + redirectUri + "\n"
+ "&scope=" + scope + "\n" + "&scope=" + scope + "\n"
+ "&state=" + state; //+ "&state=" + state
;
$("#requestUriExample").text(requestExample); $("#requestUriExample").text(requestExample);
$("#baseUrl").text(authUrl); $("#baseUrl").text(authUrl);
@@ -172,7 +180,7 @@
const authUrl = baseUrl + "/passwordless" const authUrl = baseUrl + "/passwordless"
const responseType = "code"; const responseType = "code";
const redirectUri = getRedirectUri(); const redirectUri = getRedirectUri();
const scope = "photos%20files"; const scope = "photos files";
const state = generateSessionState(); const state = generateSessionState();
setCookie("webauth-state", state, 5); setCookie("webauth-state", state, 5);