- [Instructor] I teased already
that authentication works differently when dealing
with single page applications and APIs.
But let's first look at how we identify an authenticated
user in our playground app using cookies previously.
In a playground application,
the user posts their credentials using the login form.
The server will then try to authenticate the user,
and if the credentials are valid,
the user ID of the authenticated user is written
to the session.
A common way to persist a user session
throughout requests are cookies.
The server now replies with the response header
that contains the session cookie.
The browser will now as with any regular web application,
load a new page and will then and this is important,
persist the session cookie directly in the browser.
From now on, and this works automatically
until the cookie expires,
it will send this cookie whenever it request a page
from the domain the cookie is connected to.
We are using cookie sessions
so we are sending a cookie with an encrypted payload
that contains all the session data,
including the ID of the authenticated user.
If you use regular maybe database based sessions,
the cookie may contain a session ID that is then used
to read the session from the database
when the server receives.
The cookie may just contain the session ID
that is then used to read the session ID from the database.
Either way, the server will receive the session cookie
and knows how deserialized the session from it.
If the session contains the user ID in our case,
the user is authenticated.
This takes place with each request from the browser.
Every time we deserialize the session from the cookie,
the browser sends to the server.
Now let's look at API calls from single page applications.
In single page applications,
the page only loads once from the server
with the initial request,
the rest is done by JavaScript and communication
with the service done through API calls with XHR or Fetch.
Let's assume we're now sending username and password
to the regular login route that used cookie-based session.
Likely and commonly when using APIs,
this API will listen on a different port than the front end,
or is even on a different domain.
The server will still authenticate the user because it gets
the username and the password, and it will, in most cases,
even reply with the session cookie as before.
Now the trouble starts.
There is a whole set of rules and restrictions that define
if a browser will accept a cookie coming back
from an XHR request in things get more complicated
when different domains or sub domains
or ports are in the mix.
In most cases,
the browser will not automatically store the cookie
and in many cases,
it won't even allow access to it using JavaScript.
This is a security measure to make sure that XHR request
cannot arbitrarily change cookie values.
Consequently, this means that a subsequent XHR request back
to the server won't contain the cookie
and this means that the request won't be authenticated.
In short, single page applications
and XHR don't mix well with cookies.
So there have to be other ways and JSON Web Tokens,
short JWT are one of them.
Let's look at the same flow and now we are using JWT.
Again, send username and password using XHR to our API.
The server will again, try to authenticate the user
and if it works, a JWT Token will be created.
Along with other properties, this token contains a payload
which contains in our case, the user ID.
The server now sends this token back with the payload,
this means this can be arbitrary JSON,
a property called Token or JWT,
where this token then is stored.
Inside our app, we can of course access this payload
and we can persist this token simply in a variable
within our application.
And here we can also, and even did this for our to do list,
store this token in a cookie
so that it survives page reloads.
Now with every request to the server,
we sent the token in a header called authentication
and a property Bearer.
So we have to do this code wise.
This is not done automatically as with cookies,
we really have to set this header in our code in UHS.
On the server then, we decrypt and validate
the token signature and if it is valid, we can again,
deserialize the user from the token payload.
Let's see how this token looks like.
So JWT Tokens have two main properties.
It's first the header,
where we defined algorithm and the type it's always JWT.
And then we have a payload,
in our case I'm sending here the user ID
and also expiration date.
So this is how long this token should be valid.
There are many more different properties that we can set
here, but in our case, we are just using this.
Now, when preparing this token,
the header and the payload are encoded using Base64.
And then they're signed using a secret
that has to be provided by us.
So this signature can then be used to ensure that
this header has not been changed somewhere along the way.
In the end, the token then look somewhat like that.
So first in green, you have the header,
then you have the payload.
And then in the end you have in orange,
the signature for this token
that ensures that it has not been tampered with.
JWT Tokens are not the solution to all problems,
and they come with their own limitations as well.
Let's take a quick look at that.
So first the main use-case of JWT are request
between services.
So not directly single-page applications,
if you have microservice architecture
and services have to send information back and forth,
JWT is a good way to do that.
In single page applications, the secret cannot be protected.
You cannot store the secret somewhat on the browser.
This means the token can be decoded.
You could read the contents of it,
but you cannot change and sign it afterwards.
If someone eavesdrops this token, the request
can still be forged unless you try different things
like adding the API of the client to the payload,
and then trying to validate that the request comes
from the same API,
but this all has its problems for instance,
if the API changes.
Yet, without the secret an attacker cannot create
and sign a token to assume another user's identity.
So this means without the secret,
I cannot create my own token.
So the server would not accept this token
because the signature would not match.
We will use the token just as a standardized means to sign
and transport the user ID similarly
as we did with the cookie session.
Additionally, and that's one reason to use it,
we will use the expiration feature,
so that tokens won't be valid endlessly.
So if we set the expiration, passport will then take care
for us that an expired token won't be accepted.
If you want to find out more, there's a dedicated page, JWT.
It also comes with a debugger where you can paste
in your tokens and then look at its contents
and I highly recommend you have a look at it.
Passport comes with the strategy for reading JWT tokens
from a request,
but we still have to first create the token and then send it
with the payload.
And we will implement the strategy as well as the creation
of the token in the next video.