Adam Bohannon
Thank you for this write up. I have one question though:
“It can only be exchanged for tokens using a secret (the code verifier created by the okta-auth-js library earlier), which malicious browser extensions would not have access to.”
Maybe I’m missing something, but where is this done in the description above? I’m setting up an auth flow using the sign in widget and the code challenge is created for me. How do I go about exchanging for a token if I’m never exposed to the code_verifier?
Ben Willkommen
I may be missing something, but it seems like the sample code would not provide any additional protection against tokens being leaked via a malicious browser extension, which seems to be the main anecdotal attack vector mentioned in this article.
Sure, it removes token from being in the browser history, but it just moves it to localStorage
by default (if I’m reading the docs correctly).
Wouldn’t that leave your SPA just as vulnerable to that particular attack vector? A browser extension that can access browser history could presumably access any of those storage options listed in the okta-auth-js
docs just as easily.
In fact, this other post from Okta seems to confirm my suspicion:
Specifically, the Authorization Code flow with PKCE does completely protect the application from the attack where an authorization code is stolen in transit back to the application. However, once the JavaScript app has obtained an access token, it will still have to store it somewhere in order to use it, and how it stores the access token will be the same whether the app used the Implicit flow or PKCE to obtain it. You’ll still need to ensure you have a good Content Security Policy and are aware of any third-party libraries you’re using in your application.
At first I thought one benefit might be that not exposing the token in the URL of a redirect would prevent a MITM attack, but this is a moot point, as the example in this post put the token in the URL fragment, which would not be sent in the HTTP request.
It seems like the primary benefit of PKCE is not that it would prevent leakage via a malicious browser extension or XSS, but that it protects the authorization code from being stolen to obtain a token: at the end of the flow, you still have all the problems inherent to storing sensitive data in the browser.
Does that sound correct to you?
Micah
The signin widget manages the exchange for you. It detects that you’ve received an authorization code and (internally) makes the POST request to exchange it for the tokens.
Micah
pkce prevents token leakage from direct interrogation of the url. If the tokens are stored in local storage after exchanging the authorization code for the tokens, you would still be susceptible to token leakage via a malicious extension interrogating local storage.
pkce doesn’t prevent the authorization code from being stolen. It just prevents malicious code from being able to do anything with that authorization code.
aaronpk
oops I mean code_challenge
and code_verifier
! Maybe they should have called them pkce_*
instead tho cause I keep mistyping that!
Hieu Nguyen
Thanks for the clarification. To me the duration that the authorization code susceptible for leakage is much shorter than the access token. Do you know if there is any solution to remedy this “browser history syncing and they support browser extensions that could be actively scanning for tokens”? I would think this is the browser eco system’s issue rather the Oauth spec itself. What are your thoughts on it? Thanks.
Vivek Mishra
Using “SPA” app type with PKCE based authorization-code flow, we get auth token and store it in Browser. Which can be stolen using something like e.g. XSS vulnerability.
Is there a way/recommended approach to use Okta “Web” application type with single page apps.
I was thinking of till getting authorization-code we can make requests through SPA and write my own LoginCallback instead of using okta-react lib based callback.
And calling token end point from an endpoint on my webserver (may be hosted somewhere else) using client-secret in the backend but then how we will validate state as initial request originated from browser, we can’t use the state passed from the UI?
Matt Raible
Hello Vivek,
I’d recommend using a strong Content Security Policy (CSP) on your web server with your SPA to prevent XSS. If your app has a backend, you could also do all your authentication logic there. I wrote a blog post recently that shows how to do this with Angular and Spring Boot. Specifically, see the Combine Your Angular + Spring Boot App into a Single JAR section.
Vivek Mishra
Thanks Matt, that really helps.
Luís Sousa
The source code used for the demo was probably updated after this post and the route for the auth callback is now:
"/authorization-code/callback", so for those of you who are following the steps to execute the demo app, where it says:
"Change the value for Login redirect URIs to http://localhost:8080/callback"
should be:
“Change the value for Login redirect URIs to http://localhost:8080/authorization-code/callback”
stan G
thanks so much for this doc!!
kelvin arbi
anyone know how to implement token validation with netcore 3.1 API?
tried this one https://www.scottbrady91.co…
but returns 'authorization failed’
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace PkceClient.AspNetCore3
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = “cookie”;
options.DefaultSignInScheme = “cookie”;
options.DefaultChallengeScheme = “oidc”;
})
.AddCookie(“cookie”)
.AddOpenIdConnect(“oidc”, options =>
{
options.Authority = “https://xxx.okta.com”;
options.ClientId = “0oaqkp3xu6sCoZljp4x6”;
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = “form_post”;
options.CallbackPath = “/signin-oidc”;
//Enable PKCE(authorization code flow only)
options.UsePkce = true;
});
}
public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
leebrandt
Hey Kelvin:
First, PKCE is designed to be used with client-side apps and mobile apps. For server-side applications (like .NET) just use the Authorization Code flow and use the Client Secret instead of the PKCE.
For validating the token. It should be done the same as in .NET Core 2.0. There is another post from this blog on that topic. https://developer.okta.com/…
I’ve linked to the validation parameters here for the Startup.cs file.
Hope this helps.
HuncholiniTheFirst
Hi,
If I have a React JS front end and a Spring Boot back end I see three
ways in which I could do a token exchange. So when a user clicks login I
could:
1) Have my React front end perform the authorization
grant without the client secret and obtain the token as recommended by
the OAuth 2.0 docs for SPAs
2) Have my React front end perform half of the
authorization grant, i.e. get the authorization code. Then pass this on
to my back end to exchange the authorization code with the client secret
to get a token back, and then pass this token to my front end.
3) Have my React Front End delegate the
authorization flow to my back end server which can perform the
authorization grant using the client secret.
Have I misunderstood something or are all of these approaches possible? What is the recommended approach here? Is it a bad idea for my back end to play both roles here i.e. request tokens and be a resource server?
Namrata Gupta
Where exactly are we storing the code verifier in our browser? Is it session storage or cookie or anything else?
Mark Justison
Question: How does this work if you wish to authorize REST calls to a backend server? I would guess you’d need to send along the code verifier and have the back end code generate a token to compare to the front end token.
If this is in the ballpark where would I find the code_verifier? I don’t see it stored in any cookie or or local/session storage.
Matt Raible
PKCE is used for authentication as a form of client secret when doing token retrieval. If you have your backend setup as an OAuth 2.0 resource server, it’ll just validate the tokens that are passed in. It can do that by downloading the JWKS from the issuer. No need for PKCE in this scenario.
Mark Justison
So I just need to pass the access_token to a jwt verifier on the backend to validate any call made from frontend to backend?
Or are you saying that in the scenario of having a backend server I don’t need pkce at all?
Matt Raible
Yes to both. If you want to enable login (not just JWT validation) on your backend, you can use a client secret instead of PKCE.
fabio Contact
Could you use both PKCE and a client secret in order to not just validate the client server but also validate the user who initiated the the authorization request?