Stream: smart
Topic: PKCE
Keller Martin (Feb 23 2021 at 20:11):
Trying to add PKCE support to my smart client, and have been using smart.argo.run, which is the only server I have access to that supports it. Currently returning a 500 from the /token
endpoint, which was working last time I tested it (about 3 weeks ago, admittedly).
I'm not sure who owns this server, but can someone verify that everything is okay with that endpoint?
Josh Mandel (Feb 23 2021 at 20:22):
The server hasn't been touched since the connect-a-thon, I believe. Right now , choosing patient standalone launch from the web UI and then launching the granular controls test app from the link at the bottom of the screen seems to show that pkce is working. Hmm.
Keller Martin (Feb 23 2021 at 20:33):
Thanks for the reply - it must be something I'm doing. I'm using the Provider EHR launch. Can't seem to get a helpful response though: Response{protocol=h2, code=500, message=, url=https://smart.argo.run/v/r4/auth/token}
Josh Mandel (Feb 23 2021 at 23:05):
Do you have a way to test with patient standalone launch? That may be the only path that works at this stage since I don't think we formally tested the others (but.. you're saying provider EHR launch was working for you before, so that's not super compelling.)
Josh Mandel (Feb 23 2021 at 23:05):
Does your error response have an empty body? Is that what message=
indicates?
Josh Mandel (Feb 23 2021 at 23:05):
Can you send along a full request to we can check for any issues?
Keller Martin (Feb 24 2021 at 15:29):
No problem - yeah EHR launch worked for me during the Connectathon. I did a ton of refactoring since then but I don't think I changed anything with the request.
Request{
method=POST,
url=https://smart.argo.run/v/r4/auth/token,
headers=[
Content-Type:application/x-www-form-urlencoded,
Accept:application/json,
Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjpmYWxzZSwic21hcnRfc3R5bGVfdXJsIjoiaHR0cHM6Ly9zbWFydC5hcmdvLnJ1bi8vc21hcnQtc3R5bGUuanNvbiIsInBhdGllbnQiOiIyY2RhNWFhZC1lNDA5LTQwNzAtOWExNS1lMWMzNWM0NmVkNWEiLCJlbmNvdW50ZXIiOiIxZTM4Yjc3MS1lYTg3LTQzNDMtYTVhOC02MDAyMjM3NGNiYWEiLCJyZWZyZXNoX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SmpiMjUwWlhoMElqcDdJbTVsWldSZmNHRjBhV1Z1ZEY5aVlXNXVaWElpT21aaGJITmxMQ0p6YldGeWRGOXpkSGxzWlY5MWNtd2lPaUpvZEhSd2N6b3ZMM050WVhKMExtRnlaMjh1Y25WdUx5OXpiV0Z5ZEMxemRIbHNaUzVxYzI5dUlpd2ljR0YwYVdWdWRDSTZJakpqWkdFMVlXRmtMV1UwTURrdE5EQTNNQzA1WVRFMUxXVXhZek0xWXpRMlpXUTFZU0lzSW1WdVkyOTFiblJsY2lJNklqRmxNemhpTnpjeExXVmhPRGN0TkRNME15MWhOV0U0TFRZd01ESXlNemMwWTJKaFlTSjlMQ0pqYkdsbGJuUmZhV1FpT2lKbU16VTVZVEpoTnkwMk1XTTJMVFEyWWpZdE9XSmlOQzAyWlRNMlpHWTBOemM0TTJVaUxDSnpZMjl3WlNJNklteGhkVzVqYUNCdmJteHBibVZmWVdOalpYTnpJRzl3Wlc1cFpDQndjbTltYVd4bElHWm9hWEpWYzJWeUlHeGhkVzVqYUM5d1lYUnBaVzUwSUhCaGRHbGxiblF2VUhKaFkzUnBkR2x2Ym1WeUxuSnpJSEJoZEdsbGJuUXZUMkp6WlhKMllYUnBiMjR1Y25NZ2NHRjBhV1Z1ZEM5RmJtTnZkVzUwWlhJdWNuTWdjR0YwYVdWdWRDOVBjbWRoYm1sNllYUnBiMjR1Y25NaUxDSjFjMlZ5SWpvaVVISmhZM1JwZEdsdmJtVnlMelV5T1RFNU1EazVMVFpoTjJFdE5EUXlZeTFpTUdRMUxUSmlNREpqTUdSa05HSTNOQ0lzSW1saGRDSTZNVFl4TkRFNE1ESTNOeXdpWlhod0lqb3hOalExTnpFMk1qYzNmUS5yME1rdVgwSmN5QVp1X3M0WDJRVUF4bmc3ajM2eHo0S1pzQTdKYzUxZU5JIiwidG9rZW5fdHlwZSI6ImJlYXJlciIsInNjb3BlIjoibGF1bmNoIG9ubGluZV9hY2Nlc3Mgb3BlbmlkIHByb2ZpbGUgZmhpclVzZXIgbGF1bmNoL3BhdGllbnQgcGF0aWVudC9QcmFjdGl0aW9uZXIucnMgcGF0aWVudC9PYnNlcnZhdGlvbi5ycyBwYXRpZW50L0VuY291bnRlci5ycyBwYXRpZW50L09yZ2FuaXphdGlvbi5ycyIsImNsaWVudF9pZCI6ImYzNTlhMmE3LTYxYzYtNDZiNi05YmI0LTZlMzZkZjQ3NzgzZSIsImV4cGlyZXNfaW4iOjM2MDAsImlkX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUo5LmV5SndjbTltYVd4bElqb2lVSEpoWTNScGRHbHZibVZ5THpVeU9URTVNRGs1TFRaaE4yRXRORFF5WXkxaU1HUTFMVEppTURKak1HUmtOR0kzTkNJc0ltWm9hWEpWYzJWeUlqb2lVSEpoWTNScGRHbHZibVZ5THpVeU9URTVNRGs1TFRaaE4yRXRORFF5WXkxaU1HUTFMVEppTURKak1HUmtOR0kzTkNJc0ltRjFaQ0k2SW1Zek5UbGhNbUUzTFRZeFl6WXRORFppTmkwNVltSTBMVFpsTXpaa1pqUTNOemd6WlNJc0luTjFZaUk2SWpaaE1qQTFOR1prT1RWa1lqRTFOVFV5WXpkbE5XWmxZalU0WlRKbU5ERXhNRFkwT1RZd1pEUXlNR1F5TWpVNU56SmtaakZoWVRZeU4yTTVZalZqTXpZaUxDSnBjM01pT2lKb2RIUndjem92TDNOdFlYSjBMbUZ5WjI4dWNuVnVMeUlzSW1saGRDSTZNVFl4TkRFNE1ESTNOeXdpWlhod0lqb3hOakUwTVRnek9EYzNmUS5LTXdvaDNSb3FVcFpfOXYyb3dubko4STRFYi1FNVNfcFhOUjY3SC01ZEZ1c01TTXNlZVFHLVhVVHJ0dEZBeExtanZpQ29naWhudjZ3U2RXS0o5eHNOaXVaa1JmZzVwb3pjTUxITHVMQ2E0YUNfUzA3T0EzWERMN1ZqOTJPS1pNZURsSGREMDBqRV9Sd3IyUDkwOGk1aHR5dWE5OElnZzNMQUFrbk0zM3dhSC0ycGViUHhsdkF1NFBrUERJZWlac0hmTVN5OFlOMWxuazUzX1ZGNUlieTZ2Yk16U0Zwa0ZpVHRKV3RTQU1LTUxwcFllUHZKZW9KVGxqX3dqaEs1bkdNNnIwUmFrTjAtbXBITDdtcUFRWW8yeEZRaUtzWmt0c2N3RTdoRFlWRUl6U0FFRnRUTFVaWWZoTXFvUS1BWUYyQXVabVdUdDNES3RvU3BPa255NUtha3ciLCJpYXQiOjE2MTQxODAyNzcsImV4cCI6MTYxNDE4Mzg3N30.sXACzTTJ8To9NOKosCdgpaaiYw1XP0wM5p7ecgHiXsg,
Connection:keep-alive,
cache-control:no-cache
]
}
Form Body: {
code: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjpmYWxzZSwic21hcnRfc3R5bGVfdXJsIjoiaHR0cHM6Ly9zbWFydC5hcmdvLnJ1bi8vc21hcnQtc3R5bGUuanNvbiIsInBhdGllbnQiOiIyY2RhNWFhZC1lNDA5LTQwNzAtOWExNS1lMWMzNWM0NmVkNWEiLCJlbmNvdW50ZXIiOiIxZTM4Yjc3MS1lYTg3LTQzNDMtYTVhOC02MDAyMjM3NGNiYWEiLCJyZWZyZXNoX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SmpiMjUwWlhoMElqcDdJbTVsWldSZmNHRjBhV1Z1ZEY5aVlXNXVaWElpT21aaGJITmxMQ0p6YldGeWRGOXpkSGxzWlY5MWNtd2lPaUpvZEhSd2N6b3ZMM050WVhKMExtRnlaMjh1Y25WdUx5OXpiV0Z5ZEMxemRIbHNaUzVxYzI5dUlpd2ljR0YwYVdWdWRDSTZJakpqWkdFMVlXRmtMV1UwTURrdE5EQTNNQzA1WVRFMUxXVXhZek0xWXpRMlpXUTFZU0lzSW1WdVkyOTFiblJsY2lJNklqRmxNemhpTnpjeExXVmhPRGN0TkRNME15MWhOV0U0TFRZd01ESXlNemMwWTJKaFlTSjlMQ0pqYkdsbGJuUmZhV1FpT2lKbU16VTVZVEpoTnkwMk1XTTJMVFEyWWpZdE9XSmlOQzAyWlRNMlpHWTBOemM0TTJVaUxDSnpZMjl3WlNJNklteGhkVzVqYUNCdmJteHBibVZmWVdOalpYTnpJRzl3Wlc1cFpDQndjbTltYVd4bElHWm9hWEpWYzJWeUlHeGhkVzVqYUM5d1lYUnBaVzUwSUhCaGRHbGxiblF2VUhKaFkzUnBkR2x2Ym1WeUxuSnpJSEJoZEdsbGJuUXZUMkp6WlhKMllYUnBiMjR1Y25NZ2NHRjBhV1Z1ZEM5RmJtTnZkVzUwWlhJdWNuTWdjR0YwYVdWdWRDOVBjbWRoYm1sNllYUnBiMjR1Y25NaUxDSjFjMlZ5SWpvaVVISmhZM1JwZEdsdmJtVnlMelV5T1RFNU1EazVMVFpoTjJFdE5EUXlZeTFpTUdRMUxUSmlNREpqTUdSa05HSTNOQ0lzSW1saGRDSTZNVFl4TkRFNE1ESTNOeXdpWlhod0lqb3hOalExTnpFMk1qYzNmUS5yME1rdVgwSmN5QVp1X3M0WDJRVUF4bmc3ajM2eHo0S1pzQTdKYzUxZU5JIiwidG9rZW5fdHlwZSI6ImJlYXJlciIsInNjb3BlIjoibGF1bmNoIG9ubGluZV9hY2Nlc3Mgb3BlbmlkIHByb2ZpbGUgZmhpclVzZXIgbGF1bmNoL3BhdGllbnQgcGF0aWVudC9QcmFjdGl0aW9uZXIucnMgcGF0aWVudC9PYnNlcnZhdGlvbi5ycyBwYXRpZW50L0VuY291bnRlci5ycyBwYXRpZW50L09yZ2FuaXphdGlvbi5ycyIsImNsaWVudF9pZCI6ImYzNTlhMmE3LTYxYzYtNDZiNi05YmI0LTZlMzZkZjQ3NzgzZSIsImV4cGlyZXNfaW4iOjM2MDAsImlkX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUo5LmV5SndjbTltYVd4bElqb2lVSEpoWTNScGRHbHZibVZ5THpVeU9URTVNRGs1TFRaaE4yRXRORFF5WXkxaU1HUTFMVEppTURKak1HUmtOR0kzTkNJc0ltWm9hWEpWYzJWeUlqb2lVSEpoWTNScGRHbHZibVZ5THpVeU9URTVNRGs1TFRaaE4yRXRORFF5WXkxaU1HUTFMVEppTURKak1HUmtOR0kzTkNJc0ltRjFaQ0k2SW1Zek5UbGhNbUUzTFRZeFl6WXRORFppTmkwNVltSTBMVFpsTXpaa1pqUTNOemd6WlNJc0luTjFZaUk2SWpaaE1qQTFOR1prT1RWa1lqRTFOVFV5WXpkbE5XWmxZalU0WlRKbU5ERXhNRFkwT1RZd1pEUXlNR1F5TWpVNU56SmtaakZoWVRZeU4yTTVZalZqTXpZaUxDSnBjM01pT2lKb2RIUndjem92TDNOdFlYSjBMbUZ5WjI4dWNuVnVMeUlzSW1saGRDSTZNVFl4TkRFNE1ESTNOeXdpWlhod0lqb3hOakUwTVRnek9EYzNmUS5LTXdvaDNSb3FVcFpfOXYyb3dubko4STRFYi1FNVNfcFhOUjY3SC01ZEZ1c01TTXNlZVFHLVhVVHJ0dEZBeExtanZpQ29naWhudjZ3U2RXS0o5eHNOaXVaa1JmZzVwb3pjTUxITHVMQ2E0YUNfUzA3T0EzWERMN1ZqOTJPS1pNZURsSGREMDBqRV9Sd3IyUDkwOGk1aHR5dWE5OElnZzNMQUFrbk0zM3dhSC0ycGViUHhsdkF1NFBrUERJZWlac0hmTVN5OFlOMWxuazUzX1ZGNUlieTZ2Yk16U0Zwa0ZpVHRKV3RTQU1LTUxwcFllUHZKZW9KVGxqX3dqaEs1bkdNNnIwUmFrTjAtbXBITDdtcUFRWW8yeEZRaUtzWmt0c2N3RTdoRFlWRUl6U0FFRnRUTFVaWWZoTXFvUS1BWUYyQXVabVdUdDNES3RvU3BPa255NUtha3ciLCJpYXQiOjE2MTQxODAyNzcsImV4cCI6MTYxNDE4Mzg3N30.sXACzTTJ8To9NOKosCdgpaaiYw1XP0wM5p7ecgHiXsg
grant_type: 'authorization_code'
client_id: 'f359a2a7-61c6-46b6-9bb4-6e36df47783e'
redirect_uri: 'http://localhost:443/index'
code_verifier: '34b2a3a9985d2759786fbe2f8c413591e45c7a5b1abc5635b75e605e3a0327e96e47fbe9eec0eb9442c5df6ebde093dfcc445774ee665a93160b1a505f3fa9b90d1d228f3ed057e4cce581b9bea4a9bec94b077282b6a70995d97d889c9240580721201f4336c69d47e97586'
}
Keller Martin (Feb 24 2021 at 15:31):
And yes, message
is empty in the response.
Keller Martin (Feb 24 2021 at 15:35):
According to the PCKE docs, I would expect a formatted error response. If this is not expected to work, I can chalk it up to it not being implemented. If this is the case, is there a server you know of that has PCKE implemented for the EHR launch that I can test against?
Keller Martin (Feb 24 2021 at 15:35):
https://tools.ietf.org/html/rfc7636#section-4.4.1
Keller Martin (Feb 24 2021 at 16:04):
To answer your question, no I do not have a way to test with the patient standalone launch, my client app is only ehr launch at the moment.
Josh Mandel (Feb 24 2021 at 16:47):
One quick note from your example -- the code_verifier is 216 characters long but the PKCE spec says:
code_verifier = high-entropy cryptographic random STRING using the unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" from Section 2.3 of [RFC3986], with a minimum length of 43 characters and a maximum length of 128 characters.
Josh Mandel (Feb 24 2021 at 16:48):
Can you include an example with an auth code, too? That'd help check that the verifier and the challenge match.
Josh Mandel (Feb 24 2021 at 16:49):
Re: Error reporting, this is something we should look into -- it will be important for the launcher to provide informative errors, for debugging! (Right now, you could run the launcher locally with docker-compose and review the console; but that's not a great lightweight option.)
Keller Martin (Feb 24 2021 at 17:23):
The auth code in that example is the same as the Bearer auth
token in the headers. I think that's correct from the documentation? I just didn't want to put the same code twice. I was confused as to why I would need to send the same code as the token auth and in the form body.
Keller Martin (Feb 24 2021 at 17:25):
Just sent another request with a code_verifier of 64 characters:
Code Verifier: fb47fc5221317f14fa5a65e4bab82bd7790e69dd7b5d72e75fe8e812a082c5c9
Code Challenge: uXfVXKj-tcEDe7ONF03OkMFdnAuPoKeNfWPYmxea-Lc
Keller Martin (Feb 24 2021 at 17:26):
Edited my previous request body to use the authorization code.
Keller Martin (Feb 24 2021 at 17:51):
To be clear, I'm sending in the access_token
from the /authorization
response in the header and in the form body.
Keller Martin (Feb 24 2021 at 18:09):
Also, just set up the launcher locally and ran with docker. The container does not have a /.well-known/smart-configuration
, is that something I need to set up? Using https://github.com/microsoft-healthcare-madison/smart-launcher
Josh Mandel (Feb 24 2021 at 19:03):
The auth code in that example is the same as the Bearer auth token in the headers. I think that's correct from the documentation?
This doesn't sound right. Can you clarify which documentation you mean?
Josh Mandel (Feb 24 2021 at 19:04):
The container does not have a /.well-known/smart-configuration, is that something I need to set up?
The branch you pointed to is the right branch to be running (it's what's deployed at https://smart.argo.run). https://github.com/microsoft-healthcare-madison/smart-launcher/blob/master/src/wellKnownSmartConfiguration.js is the code for the smart-configuration page, and https://smart.argo.run/v/r4/fhir/.well-known/smart-configuration shows it in the publicly hosted instance. I'm not sure what may be happening locally.
Josh Mandel (Feb 24 2021 at 19:05):
(BTW, thanks for digging in here, and sorry the debugging is so challenging.)
Keller Martin (Feb 24 2021 at 19:52):
I think I'm confusing access_token
with authorization_code
. Up until this point I assumed they were the same. I believe there is some magic happening between the original /authorize
request and when my client receives the access_token
. Looking at this https://tools.ietf.org/html/rfc6749#section-4.1.
Seems that the authorization_code
is used in a behind-the-scenes handshake here. Then I receive the access_token
in the token response.
I'm using the js fhir-client. Do I need to make another call to the server's /authorize
endpoint to get this authorization_code
? Surely I can somehow grab that code during the original authorize request.
Keller Martin (Feb 24 2021 at 20:13):
Yep. Seems like it's the access_token
in the bearer auth header, and the authorization_code
in the body. https://tools.ietf.org/html/rfc6749#section-7.1
Keller Martin (Feb 24 2021 at 20:18):
Also, I'm using /authorize
and the fhir-client.js method FHIR.oauth2.authorize()
interchangeably. I assume that's correct.
Isaac Vetter (Feb 24 2021 at 22:45):
Hey Keller, fyi - I'm pretty sure that this sandbox supports PKCE.
Josh Mandel (Feb 24 2021 at 22:53):
The code
you're pasting in appears to be an access token, not an authorization code.
Josh Mandel (Feb 24 2021 at 22:54):
Though I don't understand how you'd have an access token before calling the access token endpoint. Is it possible you've mixed up the values in the example you created?
Keller Martin (Feb 25 2021 at 01:22):
I am calling the /token
endpoint after I call the /authorization
endpoint.
Workflow:
1. EHR launch calls my client's `/launch` endpoint
2. Client calls FHIR.oauth2.authorize with the `code_verifier`
3. Server calls the redirect endpoint with an access token (the client never sees any `authorization_code`)
...
6. Client calls `'.well-known/smart-configuration` to get `/introspect` and `/token` endpoints
7. Token introspection
8. Client calls `/token` endpoint with the `access_token` from step 3.
Seems like I'm doing something wrong here by the way you are responding. Steps 1-3 are in a javascript client, and the rest of the work happens in a backend Java app.
Keller Martin (Feb 25 2021 at 01:23):
@Isaac Vetter thanks for the reference, I'll check this out tomorrow
Keller Martin (Feb 25 2021 at 01:26):
@Josh Mandel Yes it is, I was working under the assumption that access_token
and authorization_code
were the same thing. I realize this was a misunderstanding on my end, but I have never seen any authorization_code
and am not sure how to get it. The response from the /authorize
endpoint only returns an access token and a refresh token.
Josh Mandel (Feb 25 2021 at 01:45):
I think you may be a bit confused; the authorization response includes a URL parameter called code
, and that parameter conveys the authorization code to your app. That's the one you include in your request to the token endpoint.
Keller Martin (Feb 25 2021 at 05:21):
Yep I was confused. I'll grab the code from the URL, thanks.
Keller Martin (Feb 25 2021 at 14:53):
Well that was it - thanks for the simple solution. Just made a successful /token
request to the server. Thanks for diving into that with me @Josh Mandel !
Josh Mandel (Feb 25 2021 at 14:56):
Hurray!
Josh Mandel (Feb 25 2021 at 14:56):
(Only downside is that now improving our error responses is falling down on my priority list ;-))
Brian Postlethwaite (Oct 20 2021 at 02:04):
I'm also adding support for PKCE in my client app too (and a server) and looking for reference material on implementing it in javascript inside the browser.
The only part I'm really after is what goes into the function:
var encoded_code_challenge = encodeHash(code_challenge);
Is it really this simple, or have I stuffed something up
window.crypto.subtle.digest('SHA-256', encoder.encode('2972734071')).then(hashBuffer => {
const uiArray =new Uint8Array(hashBuffer);
encoded_code_challenge = btoa(uiArray);
});
and the .net side to verify it (which isn't working)
var computedChallenge = Convert.ToBase64String(hasher.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(code_verifier)));
if (computedChallenge != _context.CodeChallenge)
...
(I think the .net side is right, but pretty sure the javascript is wrong)
Brian Postlethwaite (Oct 20 2021 at 02:26):
Should I just be using base64-js or some similar package?
This is what I ended up with (in the code above)
<script src="https://cdn.jsdelivr.net/npm/js-base64@3.7.2/base64.min.js"></script>
encoded_code_challenge = Base64.fromUint8Array(new Uint8Array(hashBuffer));
If there's an easier way without the other package, interested to know.
(and I also URL encoded the result of the above too)
Brian Postlethwaite (Oct 20 2021 at 02:42):
Was there a good way anyone else has done the random string generation too?
Brian Postlethwaite (Oct 20 2021 at 02:51):
Works while testing on https://smart.argo.run/ with PKCE enabled
Test Launch URL https://smartqedit4.azurewebsites.net/ts/Tester/smart-launch.html
Test Redirect URL https://smartqedit4.azurewebsites.net/ts/Tester/smart-index.html
Josh Mandel (Oct 20 2021 at 03:05):
https://www.npmjs.com/package/pkce-challenge if you don't mind taking a dependency, or you can read through it for example of how to create the challenge from the verifier in any case.
Are you base64url encoding to create the challenge? From the examples above I don't see where.
Josh Mandel (Oct 20 2021 at 03:09):
Or: https://github.com/microsoft-healthcare-madison/client-js/blob/68ddd4c2c2c6c594b9ef467e70ac46754f5f350b/src/security/index.ts#L37-L43 is the code I put together at the last connectathon
Josh Mandel (Oct 20 2021 at 03:15):
Works while testing on https://smart.argo.run/ with PKCE enabled
Can you clarify what works? When I try the launcher with your example launch URL I see
smart-launch.html:6 GET https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js net::ERR_ADDRESS_UNREACHABLE
in the client page
Brian Postlethwaite (Oct 20 2021 at 03:31):
Brian Postlethwaite (Oct 20 2021 at 03:34):
And yes, the Base64URL encoding was added after I posted that original snippit.
Josh Mandel (Oct 20 2021 at 03:55):
Excellent, that link works for me. I thought you were saying that it was working for you without the base6urlencoding, which would have indicated to me that there was a problem with the launcher accepting pkce parameters without properly checking them :-)
Last updated: Apr 12 2022 at 19:14 UTC