-
Notifications
You must be signed in to change notification settings - Fork 4
Lesson 13: Authentication
Speaker: Aron Berezkin
-
Pull Request
- Pull Request Draft - Advanced: Authentication step 2 – API Routes (Won't be merged to the main branch)
- Pull Request Draft - Advanced: Authentication step 3 – Middleware route protection (Won't be merged to the main branch)
- Recording (Google Drive)
- Slides (Google Slides)
- Slides (PDF)
Knowing good authentication patterns is a super important part of Frontend development, because we will face authentication implementation almost in every app. While it feels scary, mostly due to its security connection, when we outline and follow a reasonable implementation framework, the complex connected map of interactions becomes just series of steps to fulfil.
Commonly we use authentication to identify the user and based on that show them, or prevent them from seeing, particular content in the application. This can be extended to actions they are (or not) allowed to take. Good authentication options improve UX by making the user feel safe and allowing them to smoothly use the website without unnecessary interruptions, such as inputting credentials on every reload unless they specifically choose to do so. A good example is a remember me option when user gives explicitly permission to persist their details in the browser, or restoring browsing website history after being signed out in the middle of a particular flow and consequently logged in back.
Common Authentication Methods:
- credentials
- social login
- magic link
- one time password (otp)
- biometrics
- authenticator apps
- 2FA combination of above
For a Frontend Engineer all these methods usually yield authentication tokens that we use when communicating with the backend API to authorize responses. Commonly we use JWT tokens which are issued, signed, and/or verified by the backend.
Access Token - is issued after successful authentication and used by Frontend until it expires. In case of expiry, a request to backend should fail with a predictable status 403 (401 is received when access token is not provided at all). How long is the expiration date depends on the security profile of the app - for financial application you should expect length in minutes or hours, while for non-security heavy apps it can be even a month or longer. Every token should have expiration date though. We usually append access token to request's Authorization header.
Refresh Token - is also issued after successful authentication and used until its expiry, which is usually significantly longer than for the access token. It has a single purpose to be sent to the API endpoint which will issue a new access token upon refresh token verification, allowing the users to continue their session without interruption.
LocalStorage - is accessed on the global window object and basically reflects synchronous browser storage which allows retrieving items upon page reload, for example to retrieve the access token and provide it to our API client. It is susceptible to Cross-Site Scripting(XSS) vulnerability when malicious javascript in our app/website can retrieve the tokens from localstorage and send them to an external endpoint/database. Losing refresh token this way would be very troublesome. Server cannot interact with localstorage, therefore it allows client authentication flows only.
Cookies - can be accessed by javascript from the client but usually they are used in server side environment only, when backend sets a particular cookie on the API response and this cookie is then automatically attached to every API request between Frontend and Backend without us Frontend engineers doing anything. This is quite handy place for refresh token and not that much for access token because cookies are susceptible to Cross-Site Request Forgery (CSRF) vulnerability when a hacked makes the user initiate requests they don't want to. With cookies we can implement both client and server authentication flows.
App Memory - and by that I mean just pure const or useState, which is probably the safest way how to don't allow anyone to hijack our access and refresh token, but obviously this way the tokens cannot be persisted. Therefore it is a good fit only for access token.
Every approach will have some security risks and therefore we always can only make the best effort to mitigate certain vulnerabilities. One the good approaches to tokens storage is to keep refresh token in httpOnly, secure, sameSite cookie and access token in app memory only. This way on every page reload we would lose the access token but thanks to having access to the cookies on the server side, we could generate a new one and provide it back to our client application - this flow fits Next.js SSR capabilities very well. A very good overview of such approach can be found in this hasura article.
Checklist and approach to come bit later