Adding middleware to the node server to decode and verify the token sent from the client. And tidying up the rest of the application. Finally, a walkthrough of the whole process.
Kent C. Dodds: [00:00] OK, so now here we have our application. Let's just make sure it's all working here. We can login, we can logout, we can get users whether or not we are logged in. This is what we are trying to protect. If we look in our local storage here, because I logged out there's nothing in here, but if I login again it is in here. I logout, it goes away. If I'm logged in and I say get user, then I'm sending the authorization header with the token that the server gave to me in this login request.
[00:36] Now what we need to do is set up the server so that it will take this authorization header into account when this get user endpoint is hit. This is actually very simple with a module that we have called express jot. We'll go ahead and install that npm install Express Jot, and then up here we'll simply require express jot, and then it's as simple as an app.use Express Jot. This will take a secret, and we'll use the same jot secret.
[01:19] Because this is going to be verifying our tokens, we need to verify it with the same secret. That's actually everything that we need to do. However, when a user is logging in, they obviously won't have a token, and so we need to add unless, on this middleware, and we'll say the path is in this array/login. Now Express Jot, under the covers, is going to intercept all of the requests that come in.
[01:52] It will take this authorization header, with the bearer and the token, it will decode that token using the jot secret. If it does decode properly, and the signature is verified, then it will add user to the request object, and the user property will simply be the decoded json object. We're going to see what that looks like here in a moment, but let's go ahead and see what breaks here.
[02:20] I'm pointing to the server directly, to the random user endpoint, and if I refresh here, now I'm getting an unauthorized error, "No authorization header was found." If I refresh here, I'm not logged in, I say get...Oh, except I think that I am logged in. Refresh here, OK, "No authorization header was found," and the air is coming from this Express Jot.
[02:45] With just these two lines, we were able to take our application and add Express Jot. Obviously we do have this jot signing, so that's a couple of extra lines, but it's actually very simple to add this authentication scheme to a node application. We do have a couple of more things that we want to do on the front end to make things a little more sensible. If I go ahead and login here, I login. N
[03:09] Now I have, in our resources, I have that auth token. We'll clear our network here, say get user, and I can get the user. We can see on here I have the authorization header, so that's why I was able to get through. If I refresh the page, you'll see it's not showing that I'm logged in, but when I get the user everything works out fine, because I do have that token locally in the client.
[03:35] What we want to implement now is to be automatically logged in when we refresh the page if we have the token locally. Let's go to our app, and we'll have this initialization step in our controller, and we'll say, "user factory.getuser" then we'll say vmuser is equal to the data that comes back, and so let's go to our user factory, and will add a getuser function here, and we'll define that down here. We'll simply say if auth token factory get token, so if there is a token in existence, then we can return $HTTP.getapiurl/me.
[04:30] Otherwise we'll return $Q.reject, with the data client has no auth token. We'll need to inject Q here. Now we need to implement this /me endpoint. We'll go back to our server and just right here we'll say app.get/me. Because we have this Express Jot here, and the only path that is being excluded is the /login, we can know that this /me is being protected by this Express Jot, so no request will come through to /me unless they pass through this Express Jot first.
[05:20] We can rely on the fact that the user is authenticated at this point, and Express Jot will put the decoded token onto the user object, and so I can simply just say "response send rec.user." This is very simple, so all of this should work, assuming I didn't break anything. Let's just double check in our resources. We have the auth token, now if I refresh...There we go, Kent C Dodds, we're automatically logged in, and we can get the users right from the get go.
[05:56] If we logout, there's no token in here. Oops. We'll refresh, and now we're not able to get any users, and so that's the basic gist of what we're trying to accomplish in the series. Actually, there's one other thing that we want to do, and that's just for the fun of it. Well say ng show, or actually we'll say a hide when there's a vm user we'll hide this, and ng show when there is a vm user. Now let's refresh.
[06:28] Now it makes more sense. Get random user. We'll login, and we'll login with the wrong password, password/username incorrect. We login without a password...OK, so all of our error handling is working. Now we login, here we have...and now we can get the user, and we can logout, and go back and forth that way. If we just hit the endpoint directly, "No authorization header found," and so this application is a working application, and that is what we've created using Jots.
[06:59] Let's just take this full circle and explain what's happening here. We'll refresh the page here, we'll clear out here. We login with kentcdodds, and P as our password. We send that with this login request. Let's make this big here. The payload is the password and username. Then the server returns us a signed token with a secret that only the server knows about, and the user.
[07:28] Then behind the scenes on the client we add an auth token to local storage, and that we have an auth interceptor that will intercept every request that we make with HTTP, and add this auth token to our request. If we look at our network tab here again, say "get user," we get the user, and here in our headers we have that authorization header with the bearer and the token. Then when we want to logout, let's take a look at our resources here.
[07:59] We say logout, and that auth token is removed from local storage, and the user on the view model is set to null, and so our view updates as well. That's the application, that's Jots with Angular.
[08:12] [cuts off]
Make sure you have this initialization step in your controller: https://github.com/eggheadio/egghead-angularjs-aunthentication-with-jwt/blob/master/public/app.js#L16
UserFactory.getUser().then(function success(response) {
vm.user = response.data;
});
Is it possible to use JWT for anonymous authentication (e.g., allow a user to only add/remove items from his own shopping cart, but not require the user to create a a username/password)?
Absolutely.
Thanks. So would I just skip the login part and handle everything else the same?
That sounds like it would work well to me! Good luck!
How would I go about authorizing routes with ui-router and this approach? say, a user gets redirected to a dashboard after they log in.
Good question. That would make a good lesson. What I have done is have a loading state that gets the user from the token and forwards them to the dashboard or the login. That's my default state.
One of the worst thing about this website ( which I've subscribed for Premium btw ) is that none of the teachers actually explain anything , they just do a fast-forward coding , like there is someone chasing them !! For the love of god ( and the money that I've given to you) slow down !
Thanks for the tutorial!
I had a question about handling expired tokens. How would you handle, on the client, a token that has expired?
You can have another interceptor that checks a failed response for some message (or status code) that indicates the token has expired. In that case, remove the token from localStorage and send the user to the login screen.
Kent,
Excellent tutorial. I do have a question on the .unless() usage. It seems very straightforward but for GET queries which pass parameters as part of the URL I've been having an issue where the .unless doesn't seem to think that it matches my values.
What I mean is this:
router.get('/:type/:email/:date?/', function ...
That is my route, off of "/v1/report"
I added the "/v1/report" as a test so my query could still work from the browser, however, it does not.
app.use(expressJwt({ secret: config.SECRET })
.unless({ path: [ '/v1/auth', '/v1/is-alive', '/v1/report' ] }));
When I add the full parametrized URL it works correctly. Given that this is a common issue, how is one to work with unless and parametrized URLs?
Hi I'm getting problems using the express-jwt module. As soon as I enter router.use(expressJwt({ secret: 'jwtSecret' }).unless({ path: [ '/login' ]}));
I get an Error: UnauthorizedError: No authorization token was found<br> at middleware
. Without the expressJwt line above, everything works and the Authorization header is present and the token is created fine and present in the auth header. It seems to me there is a problem with the unless()
because when I put the expressJwt middleware on just the protected resources rather than using "app.use" (or router.use in my case) it works great. For some reason the '/login' path is not being excused from the authorization I think. Any ideas?
It's likely the APIs have changed since this video was recorded. I recommend looking at the docs and reaching out for support from the community.
Thanks for doing this series. Any plans on updating it to Angular?
Hi Ken,
I have this working. Problem is if I refresh the page I am no longer logged in even though the localStorage still has my jwt token. How do you persist the authentication? It sucks having to re-authenticate every time. I've tried a bunch of different things and all of them have failed me.