 Hi, everyone. I'm going to show you how to use both JWT and opaque access tokens in the same spring boot application. Along the way, I'll talk about the pros and cons of each. The app we are building will get the benefit of each type of token. OAuth access tokens are, by spec, opaque, meaning it's just some string. However, many OAuth providers will actually use JWTs for their access tokens. This is so common in fact that Spring Security actually supported JWT tokens before they supported opaque tokens. The main benefit of using a JWT is that they can be validated locally, whereas opaque tokens must be validated remotely in which you incur another HTTP call in your application, adding latency. However, the downside of this is that JWTs are basically stale. Any token you validate locally are stale, meaning that at some point after you were given the token that token could have expired, the user could have logged out, been disassociated with it somehow. So if you're using JWTs, you want to keep the lifespan short, say five minutes. That way, at most, the token is only stale for a few minutes. So I'm actually going to build two apps today. So the first one is actually just going to retrieve a token. Now, this represents your application, whether it's a mobile app, a spa, maybe an API gateway, some sort of application that is in charge of getting an access token. And for this, I'm just going to create a simple Spring Boot application and I'm going to dump the access token on the screen and we'll talk about that. So I'm going to use IntelliJ for this whole project. I would typically probably use command line or maybe start.spring.io, but to keep everything in this video, I'm just going to use my IDE. So I'm going to create a new project and I'm going to use the Spring initializer. I'm going to use Java 11, but the same process would work with Java 8 as well or newer. So I'm going to call this token viewer and it's going to use Maven, Java, and I'm going to package it as a JAR and we're going to leave all the other everything else to the fault. And for this, we want to use web, so Spring web, and we're going to use OAuth client, OAuth client. And then hit next, I'm going to create this project. Now that my IDE is finished loading, I'm going to configure the application to use OAuth. So I'm going to use Octa for this. The steps on how you get your configuration variables changes between vendors, but the theory and the types of configuration is all the same. I'm going to use the Octa CLI to handle all of the configuration for me. So if I open up a terminal, you can install the Octa CLI by using brew or flat pack or chocolatey. The links for all that will be in the description below. Once you have that installed, you'll need to register with Octa for a new account. If you already have one, you can use Octa login. If you don't, you can just use Octa register and then it'll prompt you to enter some basic information. This will take a minute. You'll get an email verification code, hop over to email and grab the code and you can just paste it in the terminal. Then follow the link and you can set up a password. Once you do all that, you're ready to create an application. So you can type octa apps create and this will walk you through a little wizard. So I'm going to call this token viewer and then we're going to build a web application and we're going to use spring boot. So number two, and this is the default callback URI that spring boot uses. So you can just hit enter again. So this will configure everything for you and it will write to your application properties file. Now this file does contain a secret. So you don't want to check that in spring offers a whole bunch of different ways to deal with secrets and files and manage across environments using environment variables spring cloud config. Other property files, there's a whole host of options. For this example, I'm just going to use a properties file. So we can open that up and you can see take a quick look. So it has a client ID, a client secret and an issuer URI. So these were all just created for me. And if you just create a password, you'll know that this is your octa org URL for to give the backslash here. That's just an escape encoding for properties files. All right, so we're almost done with this little application here, we're going to create a simple rest controller. Otherwise, they won't do anything. So I'm going to create a little rest controller that's going to dump the access token that my user gets to my browser. Now this may look a little funny. So this is actually just a spring annotation. So I'm going to use the current authorized client, which was octa. Now this octa actually lines up with the value from my properties files. If you remember that octa appeared here. So if you're using Google or Facebook, there'd be a different value here. But in practice, it's it's the same. So once I have the client, I can get the current access token and then I'll get the token value as a string. Just to make things easy, I'm going to format it so I can cut and paste it into my terminal and I'll have a token environment variable with the access token. So I'm going to start this up. Then you can head over to your browser. I'm going to use an incognito window, but you don't have to. So if I go to localhost 8080 slash token, it'll prompt me to log in. And this will be the same account that I just created. And there we go. This is this is a very ugly application. But you can see it'll export a token. And this is a JWT string. And we're going to use the same value as the JWT and the opaque token. So if I copy that, get rid of my browser. Now that we have an access token, we're going to create a REST API that will consume that access token. So just as we did before, I'm going to create a new project. Again, I'm going to use the spring initializer. You could use start dot spring dot IO or any other means you're comfortable with. And again, I'm going to use Java 11. And we're going to call this one to tokens. And again, Maven Java Java 11. Click Next. Now for this application, we need spring web again. It's a spring web. And this time we're going to do OAuth resource server. Typically, your REST API is a resource server. You can access that with an API token or in our case an access token. So you just click Next and continue through here. And once you have everything, we're going to have to make a couple of small changes. So for one, we're going to add another dependency. So open up our palm. Of course, if you're doing this cradle, process is basically the same. You grab your dependency. In our case, it will be this Nimbus dependency. So spring security uses Nimbus under the covers. And if you want to use opaque access tokens, you need to include this dependency. Now we need to jump over to our application properties. We're going to configure a few things here. First, since we have two applications running, this one's going to be running on port 8081. The default is 8080, which our other application is using. So we'll do server dot port and just call it 8081. Next, I'm going to turn on a little bit of logging to show you which outbound requests are actually being made. So this is logging level. And we're going to do org spring framework web client REST template. And we're going to set the level to debug that will log every outbound call. And now, of course, we need to configure our OAuth application. So we're actually going to configure both the JDBT configuration and the opaque token configuration all at once. That way we don't have to come back to this. So the client ID and secret are going to be the same as your other application. So you can copy and paste them over here. But the actual property ID is going to be different. So let's start with that. I'm going to use the type completion to find it for me. So it's OAuth resource server. So I have an ID and I have a secret. And then I'm also going to need one more value for this. So this is going to be resource server opaque token. And this is going to be introspection URI. So if I grab the same values from my other application, you can see I have a client ID. And don't worry, I'm going to delete these this client ID and secret after I record this video so they won't be valid. Just make sure your secrets aren't leaked anywhere. So the client ID, the client secret, you also need the issuer. But in our case, it's not actually the issuer. This is the introspection URI. So you just need to take your issuer and then add slash V1 slash introspect to the end of it. And then to configure the JWT, basically the same thing, except we just need to configure the issuer for this one, which is the same URL we just had. I want to remind you keeping secrets and files is not a great idea. So use caution here. So now that I have my properties configured, let's jump back over to our application. I'm going to create a simple REST controller. It's a new Java class called simple controller. We'll annotate this with REST controller. And then I'm going to use the tried and true Hello World. Now you may laugh that I'm using a Hello World example, but this allows us to keep the focus on what we're talking about, which is access tokens. Everyone knows how to make a Hello World example. So I have one get request. So if I make a get request to slash, it's going to return hello. And if I make a post request to slash with the included message, it's going to say hello message. We'll get back to this in a minute, but I have a get and a post request. All right. So if I start up the application, if I did everything right, it should work. You can see one HTTP call has already been made, and this is to a well known endpoint. This is going to allow spring security to fetch information about my OAuth IDP, such as where to find the keys for the JWTs that I'm using, et cetera. So if I go to a terminal, remember, this is where you're going to need to paste in that ugly web page we made earlier, which was export token and then some giant JWT string. So you said enter. I'm going to use HTT PI to make my request for me. It's very similar to curl. I find it a little more user friendly, but you can do the same thing with curl. So I'm going to make a request to port 8081. Remember, we have two applications. The REST API is running on 8081. So if I just say slash, that's going to make a get request. Now this request should fail because out of the box it requires security. So here we go 401. So now if I add the authentication header, so authorization bear token. And there we go. So there we have a hello world. If I go over to my log, you can see one additional request has been made, and that's to get the keys. So the first time a JWT is accessed, I need to get the public keys. So they're fetched and then they're cached. So no additional calls have been made. So if I clear this log and make another request, you'll be able to see there's no new log entries. All right, so that's great. So that was JWT's. Now let's change this application to use an opaque token. I'm going to create a new security configuration class. So once again, new class. I'm going to call this example web security configur adapter. I know it's a long name, but bear with me. So this class is going to extend, of course, the web security configuration adapter, configur adapter. And then if I implement the methods, oops, override method, sorry, we're going to override the configure HTTP security method. And in this, we're going to drop two lines. This basically represents the application we just ran. So all requests are authenticated. So authorize requests, any requests authenticate them. And then this next line is going to configure JWT's. Now, since we just ran that example, this wouldn't show us anything new. So if I swap this out to opaque token, we're going to start using the opaque token configuration. So once again, I'm going to click run. Whoops. Of course, I need to annotate this as a configuration. And I'm going to restart my application. Now, hopefully this time I did it right. So you can see once again, a request to the well known endpoint was made. We still have the JWT properties in there, but we're not going to use them. So if I swap, if I go back over to my terminal, and I make that same request again, again, I'll get a hello, nothing surprising there. But if I go over to my terminal, you'll see that a post request to the introspection endpoint has been made. Now, again, if I clear all of this, and I make that same request again, you can see that another request has been made. So as you can see, this adds a latency into my application. But I know the token is not stale. So there's potential that the JWT could be stale. Never remember security is always a balance of many things, right? So we have performance, usability in the actual making sure we're locking this down our resources. To get the best of both worlds, I'm going to configure all my get requests to use JWTs and all my post requests to use the opaque token. So this way anytime I'm going to mutate data. So the idea being that post requests generally change data. So anytime data changes, I'm going to use the opaque token and any get requests, which are typically fast, I'm going to use a JWT. Now for your application, this may be different. Maybe you want to change resources based on pass instead of request methods. That's up to you. This technique will work either way. So we need to add a few things to support this. First, Josh Cummings from the spring security team put together an authentication manager resolver. Again, that's another mouthful. But that allows us to map some sort of resolution to which type of authentication we want to do. So let's create that class. So I'm going to cheat. I'm going to paste this in here. So you don't have to watch me type this, this long thing. It's only a couple of methods. Again, all of this code is on GitHub. I'll have links in the description below, but I'm going to quickly walk through this. So first we have a map of authentication managers. So we have this request matcher, which is going to match any servlet request to an authentication manager. So as we get down here, we'll see that when I call this resolve method, it's going to loop through all my keys and see if any of them match. And if it does, it's going to return the authentication manager that's mapped to that key. And then there's going to be a sensible default here. And I'll get to this in a second. All right, so now we actually need to make use of this class. So we're going to jump back into our security configure, which is pretty, pretty simple right now. And I'm going to create a custom authentication manager using this one we just created. So again, interest of time, I'm going to use a live template here. If you're not familiar with live templates, just Google IntelliJ live templates and it will walk you through it. Basically you can create little macros to do all this stuff for you, which are great for presentations or if you find yourself doing the same thing over and over again. So what I've just created is an authentication manager resolver, which again uses that class we just created. And I'm going to get a list of methods. So head, get and options. So those are read only HTTP methods. And if my request, my incoming request is one of those types of methods, I'm going to associate with JWT. We're going to get to this in a second. And then my default is going to be opaque tokens. So this is important here. This is going to fall back to the more secure option. So I only want to allow, you know, specific things to be the faster JWT option. But if any HTTP methods were added in the future, I would have to explicitly allow them. Again, this just allows the default option to be the more secure option. So I have a couple more live templates I can use. So I'm going to call this one, JWT auth manager. And this is going to create an authentication manager based on the defaults that spring uses. So if you look up the spring source, this is essentially what they're doing. And the same thing with opaque. And of course, now I need to inject these values. So these are already available to me in the spring context. I don't need to trade them. So we'll just inject them and forgive me. I'm going to use field injection here. I'm not a fan of field injection, but it works great for demos because it keeps the code more compact. So private JWT decoder and the other one auto wired. Private. All right. So hopefully there's no errors. This error is actually a false positive. There's only one of these. Yes, of course it does warn me about my field injection. Like I said, it's okay. So now I go back to my configure method and I'm going to remove this opaque line. And I'm going to change this to the authentication manager resolver. And of course, that's the one we just created. So now we've linked everything together. Just to walk through this one more time, I have a custom authentication manager. Every time I get a head get or options request, I'm going to validate that request my JWT authentication manager. And for all other requests, I'm going to validate them remotely using the opaque token. So there's one more thing I want to do. So I mentioned before we're using JWTs and there's no real spec behind how a JWT is used for an access token. Now there is a spec behind the format and how to validate a JWT, but that's not use case specific. Every vendor that supports JWT access tokens has a recommendation of how you should validate them. Again, in addition to normal JWT validation. So if I hop back over to my application, I'm going to create a bean. And again, just so you don't have to watch me type, I'm going to speed this along. So this is if I just create a bean with a JWT decoder, so automatically get configured. So Octa uses again a V1 slash keys endpoint. And all of this is basically the out of the box default configuration. So the timestamp configuration looks a little funny here because you would think that's baked in. It's not because if you have clock skew issues, you can actually add a duration here to deal with an additional clock skew. So by default, you get a minute where the clock skew. You could change that or lower it. What this means is that the server that created the JWT, that clock might be slightly different than the clock that your application is running on. Generally everybody is using time servers, so the difference is minimal, but there is likely some sort of drift. So you can change that if you want or reduce it if you need to. And again, the issuer needs to be validated. And now this is where we get into the customization. Audience claim. And by default, if you create a new Octa account, it's apis colon slash last default per the JWT spec. This value could be a string or an array. And this just validates that and has a nice little error message if it's wrong. Again, the details of this aren't super important for this demo, but I do want to point out each vendor will have some sort of recommendation and you should be able to do all of this for you. But for this video, I'm just using the out of the box spring security. So now if I start up my application again and go back to our terminal, I'm going to run my get request and I'm going to check my log. You can see one request to the keys endpoint. I'm going to clear this out and I'm going to make another get request and there should be nothing in my log. Next I'm going to make a post request. So this is a form post and I have the message parameter here set to YouTube. So if I hit enter here, we should see hello YouTube. Great. So now the moment of truth is I should come back to my log and I should see a call to the introspect endpoint. Now going back over to my terminal again, making the same request again makes an additional request out. Now we can do some back of the run this. So we can run the same command with time and we're going to get whatever point six five seconds. And if we do the same command with get, it's probably a quarter second or so, a third of a second. So it's about twice as fast. Now, of course, this is in a very accurate performance test. But if we run this a couple of times, you can see the numbers are fairly consistent. It's going to be slower. The post again is about point seven seconds. One interesting thing to point out here is that there's more CPU time taken on the JDBT example. And that's because there's some cryptographic validation happening on my machine to do this. Whereas if I send it remotely, there's some sort of validation happening there in my application just sitting waiting for that response. So that's the application. I'm going to show you how to monitor protection, the ease of use and speed. I'll have links to the original blog post this article is based on and the GitHub repository in the notes below. So if you liked this video and it was helpful and you want to see more like this, please click the like button and the subscribe button. That way we know you like the content and we'll create more videos like this.