Angular HTTP Interceptors

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Over the past few videos, we've been setting up  an ASP.NET Web API server that has authentication   based on JWTs - JSON Web Tokens. And then we  started using that, but we were only using it   really through Swagger, which is a tool for  developers to help us develop the Web API,   but it's not something that a real user would  use. So let's actually put a real front end   on this. And we can do that in all sorts of  different technologies, but the first one we're   going to do is do that in Angular. And so if  we take a look at what I've got running here,   you can see that we've already got this Angular  application running and we've got a couple of the   endpoints from our Web API web service. So we've  got the all reviews, we've got the summary, and   I've also put together a login page. But there's  no functionality behind that there - that's just   taking in the data. What we're going to see in  this video is how we can make all of that work.   Now just a couple of things to say about the  server itself. If we look at the code for that,   I've made a couple of changes. One is I've had to  add CORS on to the server - Cross Origin Resource   Sharing - because we're now accessing this  service from a web site that's been served   up from a different server. And so you've got  this issue, of CORS. We talked about CORS on an   earlier video quite a while back. So have a look  at that, if you want a bit more detail. Just has   to be said this is very, very relaxed in terms of  the rules, of CORS, just to get it working. You   should probably be a bit stricter than allowing  everything through. The other change I've made   is on the controller, I actually temporarily  commented out that [Authorize] attribute,   which is why we can currently, on the front end,  actually access the data, even though we're not   logged on. So let's change that one. So we'll  stop that, put that back on, run it up again.   And we get the Swagger, but we're not interested  in that - we're looking at the real front end. And   now in here we'll see that if I try and look at  either of those, we're getting an error: ‘Failed   to retrieve data’. Let's take a quick look at how  that's working in Angular. And so what I've done   is I've created this BookReviewRepository. And  if we look at the code for that, it's injecting   the HttpClient. And then we've got a couple of  methods, getReviews and getSummary for those two   pages. And they use the injected HttpClient  to go to the endpoints on our web service,   we've got the actual route there just set up in  the environment by way of configuration. And as we   saw again in many earlier videos, that returns an  Observable of the array of BookReviews that we're   getting back. We’ve then got the ReviewsComponent.  And what that does is it reads in some data from   the route, which is either going to be ‘summary’  or it's going to be ‘all’ - that's what we default   into. So we can use the same component to either  call the getSummary or the getReviews that we just   saw on that injected repository that we've got up  there. And then we do a bit of configuration of   that - we do some sorting, we blank out the error  message. And then we have a CatchError. And that's   where we're getting that ‘Failed to retrieve  data’. So I'm not looking in very much detail   at the error. We can actually see what's going on  if we look at the console there. You should see if   we bring up console and look in there, there's the  more detailed error that it was a 401. Not quite   sure why it's giving us an ‘OK’ there because  401 is not okay, but it's definitely 401. It's   definitely failing. And then finally, just in the  template for that there, we've got the <table>   that is hidden if there's an error message,  loops through the reviews on the Observable,   and then we've got the error message at the  bottom. So that's just the basics of it working.   Nothing new there. Various earlier videos to look  at to see how that works. Now let's get our login   working. And the first thing we're going to do is  we're going to add an AuthenticationService. So   at the moment, if we look at the LoginComponent,  we've got the login method, which is what's being   called when we submit the form we can see there.  And we've got the username and password that are   bound to those fields on the form. But we need  to get this working, so what I'm going to do is   create a new service. I'm going to do ‘ng g’ -  for generate – ‘service’ is the type of thing I   want to generate - and I'm going to call this  ‘authentication’. And then I'm going to say   ‘—flat=false’ so that it gets its own folder. If  you don't put that on there, you'll just get it   in the main app folder and it gets really messy  on a bigger applications. So we'll do that and   let it generate that code. And then if we go in  there into that ‘authentication’ and have a look   at that, we've just got that basic service put  together. So what do we need on there? Well, the   first thing we're going to need is to have a login  method. So let's put that in there. We're going   to call it ‘login’ and it's going to take the  ‘username’ which is going to be a ‘string’ and the   ‘password’ which is going to be a ‘string’. And  it's going to return an ‘Observable’ for a ‘Date’.   If you remember what we got back when we  log in to our server, we get back the JWT,   and we also get the DateTime telling us when it's  going to expire. So we're going to display that   expiry time. And that's why we're coming back  with the date. We need to get hold of the import   for that, that's easy enough, and then what we're  going to do here is we're going to need to make   use of the HttpClient so that we can talk back  to the server. So that means in our constructor,   we're going to have to have a ‘private’ just  call it ‘httpClient’ of type ‘HttpClient’.   We've already configured that in the AppModule,  because we've already been using that for the   ReviewRepository that we were looking at to get  the data. So that's fine in terms of that. So   that's what we've got there. And so now we've  got to make a request on that, and we're going   to do a little trick that I always do when I'm  working with Observables. Although ultimately,   we want to return the Observable, because the type  of an Observable keeps changing, whenever you do a   ‘pipe’ operation on it, it gets quite confusing  in terms of the errors. So I find it much easier   to actually just create a local variable so we  can see what type it is as it goes by. And then   once we finish this off, and that will be of type  ‘Observable<Date>’, then we can just return it   instead. That just makes the coding bit easier. So  I'm going to say ‘ = this.httpClient’. So there's   the httpClient we injected. And then remember,  it was a POST. So we can always check up with   the Swagger to see that this is going to be the  login. And we've got the ‘post’ there. So all   that data is there if you want to look it up. And  so it's going to be a POST. It's going to return,   well, we've actually got to put in a class,  and we're just going to in here - in this   DTOs folder where we’ve already got the model for  the BookReview - let's just add a new file. And   we're going to call this ‘login-response.ts’.  And in there, we're just going to say ‘export   interface LoginResponse’. And that's  going to contain the ‘jwtToken’   which is going to be a ‘string’, and also the  ‘expiration’ which is going to be a ‘Date’.   And that is just mimicking the structure that we  had in our C#. So over here, if we take a look   at the ‘LoginResponse’ there - exactly the same  thing. It's got the JwtToken, the expiration. So   that's going to be serialised to JSON, and  then deserialized back in our front end.   And so in the AuthenticationService, that's going  to be the return type from the POST. Then we're   going to need to put in the URL. So we can get the  basic URL from our environment file. So down here,   you'll see we've got the ‘environment’.  And so that's just taking us back to our   development server. So we can in here, just  say ‘environment.serverUrl’ and then on to the   end of that we say ‘/api/authentication/login’.  So that's the path we had there. And so that's   the basic path, we then have to pass in the  parameters. And remember what we're taking as   the parameters going in there. Again, we can  look at this in the C# or in Swagger. But we   can see that we've got the LoginModel. And that's  just the ‘username’ and ‘password’. And actually,   we don't really need to create a type for that in  the TypeScript, we can just do that on the fly.   So we've got a second parameter in here, which is  just going to be ‘username’ and ‘password’, which   are the parameters coming into this login. And by  the magic of JavaScript that will create an object   with properties of those names. So that'll work  fine. And let's just take a look at ‘o’ we can see   it currently is an ‘Observable<LoginResponse>’.  And this is a problem I was saying earlier:   it's not yet of the right type. So it gets a bit  confusing if we try and return it, because what   we're now going to do is, we're going to put some  pipeable operators in there. And I'm going to do   a couple of things. First thing I'm going to  do is I'm going to ‘map’ that get hold of the   import for ‘map’. And then I'm going to say ‘r’  - for response – ‘=> r.’ and then we wanted the   ‘expiration’. And so that now means our ‘o’ is the  right type - it's the ‘Observable<Date>’, which we   have there. But we're also going to do something  else. We've also got to record the JSON web token,   because we're going to be sending that back in  future requests. Now, that raises the question,   where do we store it? There's a number of options  for this. You could simply store it in memory, so   as a variable within our Angular application. The  problem with that would be it would be very, very   short-lived. If you did a refresh on your browser  that would reload the application and you'd lose   all the data and you'd have to log in again. You  could store it in a cookie, and that used to be   done a while ago, but that's a bit unnecessary,  because cookies get sent back and forth between   the server and the client, and we don't need that.  We just need to store this on the client. And so   in more recent HTML, we've got a couple of options  for that. We've got either session storage,   or local storage, which actually store them in the  browser and are accessible only by applications   that are running on this website. The difference  between them is that when you do local storage,   it persists even when you shut the browser down.  Whereas session storage, although that will   persist over a refresh or anything like that, if  you're actually close the browser and reopen it,   you lose your session storage. And I think that's  probably the best one to go with. It's a bit   irritating, because it means your login won't  persist for very long, but it's safer, because   if you're on a public machine, and you forgot  to log out, even if you shut down the browser,   somebody else could come along, go to the same  site and use your authentication. So we're going   to store it in session storage. So what I'm going to  do is put a property on this AuthenticationService   which is going to allow us to read and write the  JWT into session storage. So the very first thing   we'll do actually is just declare as a constant,  the key we're going to use in session storage.   So I'm going to say ‘private static readonly’  and then we're going to just call this ‘key’.   And we're going to just call it the ‘JWT_KEY’.  So that's how we identify this particular item   that we're storing in session storage.  Then I'm going to have a get property,   so we'll do ‘get jwt’ returns ‘string’. And then  that's simply going to say ‘return’ and then   ‘sessionStorage.getItem’. Then we put in that  key, so ‘AuthenticationService.key’. And then   we've got a slight problem that if that's empty,  it'll send back a null string. So we'll just   change that to a blank if it's empty. We're  then going to have a setter. The setter we're   going to make private because we don't want  outsiders to do this. We're going to call it,   again, ‘set jwt’. It's going to take a  ‘value’ which is going to be a ‘string’.   Doesn't have any return. And in this one, we  again say ‘sessionStorage’ and then this time,   we've got a ‘setItem’ and again, we can  take that ‘key’ and also pass the ‘value’.   So now we've got our getter and our setter. And in  this pipe, we're then going to use a ‘tap’,   which, remember, doesn't modify the Observable,  it simply does something as it's being processed.   And so in there, we say ‘r =>’ and then we're  going to say ‘this.jwt = r.jwtToken’. Okay,   so that's going to be calling the setter, and  therefore storing into the session. So we've   got the two items of information on the response.  We've got the token that we're storing, and we've   got the expiration that we're returning. And so  now that's all working, we can simply change that,   and put the ‘return’ in there and the type  matches up. And that works fine. And then   finally, all we need to do is hook that up to the  LoginComponent. So simply enough here, we need to   give this constructor and we're going to inject  into that our ‘authenticationService’ of type   ‘AuthenticationService’. And  then in the login, we can say   ‘this.authenticationService.login’ and then just  pass in ‘this.username’ and ‘this.password’.   So all easy there. Now that of course is not going  to do anything as yet, because we haven't actually   started observing that Observable. And remember,  one of the key things about Observables is they do   nothing until you actually subscribe to them  in some way. So what I'm going to do here is   I'm going to put on here a public field called  ‘expiration’. And I'm going to put ‘$’ on the   end of there - simply as a naming convention, so  that we know that this is an Observable. And then   I'm going to type it to be our ‘Observable<Date>’.  And then also, I'm going to put a question mark on   there, because it's not going to be immediately  there and so we've got to give it the option of   being undefined. And then all we’ve got to do on  here is say ‘this.expiration$ =’ the result of   the AuthenticationService, which remember was that  Observable<Date>. Then in the HTML, I'm just going   to start consuming that. So what I'll do here  is down the bottom, I'm just going to put on a   paragraph. And I'm going to give this a ‘*ngIf’  and then if that ‘expiration$’ exists. So just put   that on there. And then in there, we're going to  say ‘You are logged in until’ and then we'll bind   to the expiration. So we'll have our ‘expiration$’  then because that’s an Observable, we use the   ‘async’ pipe, and then also the date we use the  ‘date’ pipe and configure that to a predefined   one called ‘longTime’. So I'll give us the full  time, but not the date on that. And so that should   display the result. And then the other thing  we'll do is on the form itself, let's disable that   if we've got that ‘expiration$’. So once we've  clicked the login button, that's when we'll get   the expiration set to being defined. And so that  should all work. So if we save all of that - and   that looks like it's built successfully - so  if we now go up here, go to the login page,   and remember, the username was ‘Jasper Kent’ and  then ‘Pa$$w0rd’ for the password. Log that in. And   you can see what happened there, the  result appeared, ‘You are logged in until’,   but then there was a slight delay because we had  to get the result back. And you can see that set   to three hours from now, because that's what we  set in earlier videos as the expiration. And what   we can also see is, if we bring up the Dev Tools  and look in Application and go to Session Storage,   there you can see we've got the JWT stored  under ‘JWT_KEY’, which is where we put it.   And so that's all worked fine. So we've managed  to login, now we've got to use that JWT and send   it back on the other requests. So that when  we do these things, we're not getting the 401   coming in there. So that's the next step to do.  And we do that with an Angular feature called   an interceptor. And that means that rather than  having to set up the header containing the JWT,   specifically on each one of the calls - so in  the BookReviewRepository, we'd have to set it   up on this get and this get and the more calls  we did, the more fiddly that will get. But with   an interceptor, we can just define a way on  every HTTP request to have some modification   done to that request. In our case, putting in the  appropriate header. So that's what we're going to   do next. So what I'm going to do is add a new  folder that I'll call ‘Interceptors’. And then   in there, I'm going to have a new file, which I'm  going to call ‘jwt-authentication-interceptor.ts’.   And then we can start putting our interceptor in  here. So what we're going to do is we're going   to have this as injectable, so we just put ‘@Injectable’ on there to use that decorator.   And then it's going to be an ‘export class’  and ‘JwtAuthenticationInterceptor’. And that   implements an interface called ‘HttpInterceptor’.  And then let's implement that interface.   So there, we've got the ‘intercept’ method, which  is going to do all the work. But then another   thing we need to do is we're going to need to  use the AuthenticationService that we just wrote,   which is where we get the JWT from. So we're going  to put on here a constructor that's going to have   a ‘private authenticationService’  of type ‘AuthenticationService’.   Okay, so that's what we've got in there. Now  we can see on this ‘intercept’ we have a couple   of things happening. We've got the incoming  request, and then we've got the ‘next’ which   is the handler. So the idea of these is, you may  potentially want to build up a chain of them,   each of which does its own little bit to the  request. So the request comes in, we do whatever   we're going to do with it, and then call the  ‘next’ so that it can do its processing as well.   So what we're going to do in here, we're going  to need to do a couple of things. Firstly, we've   got to check that we're actually logged in at all,  because if you're not logged in, then obviously we   can't send anything. So we're going to actually  have to put on to the AuthenticationService   another property that is going to tell  us that. So what we're going to do here   is very simply just have a ‘get isLoggedIn’. It's  going to return a ‘boolean’. And then all we're   going to return is ‘!!this.jwt’. So that ‘!!’  is getting around the problem that the JWT is a   string. Now although a string can be truthy and  falsy - that's to say a null string or an empty   string is falsy - the type wouldn’t work for that.  So by doing ‘!!’, you can turn something that's   truthy or falsy into something that's true or  false, and is therefore a Boolean. So there is   our ‘isLoggedIn’. So back in the interceptor,  we're now going to say ‘const isLoggedIn =’   and then ‘this.authenticationService.isLoggedIn’.  Another thing we need to check is that we're   actually sending this request to the appropriate  server that we're logged into. Because if this   website got complicated, it could be using all  sorts of different web services, we don't want   to be sending the JWT for one web service to a  different one. That would be disastrous in terms   of security because they can then use that to get  into the other ones. So we're going to say ‘const   isToServer’ and then in this one, we're going  to get off that request, so we can say ‘req.url’   and then ‘.startsWith’ and then from the  environment, we can get that ‘serverUrl’ again.   And so that's checking that we're going to the  one that was configured for the requests. And so   we can make sure, and then having done that, we're  now going to say, ‘if (isLoggedIn && isToServer)’.   So we only want to be putting these in under both  those circumstances. And then what we have to do   is we say ‘req = req.clone’. So we don't modify  the request directly, we actually make a copy of   it. But then with this clone, we can define what  the modifications are. And what we're going to do   is we're going to say ‘setHeaders’ and then what  we're going to set is the ‘Authorization’ header.   So just the same as we saw we were setting in  Swagger or in Postman in the previous video. And   then we say, remember, ‘Bearer ’ - exactly as we  did before - and then we put in the key, so we're   going to say ‘AuthenticationService.jwt’. And  that will be the JWT that we've already set up.   So that's modified it if necessary. And then  as I say, all we need to do is call the next   one. So we simply say ‘return’ and then on that  ‘next’ that's been passed in, we say ‘.handle’   and then just pass the request through. And  so that will do all the things we need to do.   So that's our interceptor doing what needs to  be done. Last thing we got to do is register   that interceptor. So in the ‘AppModule’ in here,  where we've got our providers… So we can see we've   already got the RepositoryService. And then the  AuthenticationService, I should just point out,   we did by just leaving that ‘providedIn: ‘root’’.  So you'd probably have wanted to be consistent,   put it all the AppModule, but certainly for  this one, what we need to do in the AppModule   is we say, ‘{ provide:’ and then there is a  predefined token, which is ‘HTTP_INTERCEPTORS’   and then we say ‘useClass’ and then the name  of the interceptor that we've just created,   so our ‘JwtAuthenticationInterceptor’. The other  thing we put on here is ‘multi’ set to ‘true’.   That's to basically say we can have multiple  injected objects for this token HTTP_INTERCEPTORS.   Because remember, I said, you might want to have  multiple ones doing different bits of processing   to your request. So because we said ‘multi’, we  could put multiple ones in there and they would   be chained together in the order that they were  declared in this providers list, so if you want   to do something like that. But having done all  that, that should now be working. So if we do a   save … and we can actually see that is already  working, because remember, our JWT was stored   in the session. We haven't actually closed  down the browser, and so we’ve still got that   in there. But if I were to … well, the easiest  way is just to delete that one. And so now we'll   see that we're getting the ‘Failed to retrieve  data’ there. Now if we log in, once again, again   we've got that in there. And now we're getting  those reviews and those summaries. And if we   just take a look at the Dev Tools in there,  and go to Network and just do another request,   then we can see that ‘summary’ coming in there.  And if we click on that, scroll down to request,   and we can see there, there's the Authorization  header that's been sent with that bearer token.   So that's all of that basically working. We've  provided a login component, we're storing the JWT   in session storage – we might store it in other  places, but that's what I've decided to go for   here. And then we've used an interceptor so that  we can send that in the header when we're making   a request, and it was picked up and you can see we  can get in there. Still got a few more things to   do. We've got to do a bit of error handling. We've  got to have the ability to log out and things like   that. But we'll look at those in the next video  where we can see some more enhanced features. So   I hope you enjoyed that one. If you did, do click  like. Do subscribe and I'll see you next time.
Info
Channel: Coding Tutorials
Views: 4,203
Rating: undefined out of 5
Keywords: JWT, Angular, HTTP Interceptors, Session Storage, Captioned
Id: U2jC4JGdD8c
Channel Id: undefined
Length: 24min 10sec (1450 seconds)
Published: Fri Jul 07 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.