Free AI web copilot to create summaries, insights and extended knowledge, download it at here
5801
Abstract
cookie.</li><li><code>secure: true</code> ensures that the cookie is only sent over secure, encrypted connections (HTTPS). but since most testing environments are on localhost, it is allowed</li></ul><p id="6bc1">then executing this action looks like:</p><div id="e2cb"><pre><span class="hljs-keyword">import</span> { storeToken } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/actions"</span>;
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">onSubmit</span>(<span class="hljs-params">formData</span>) {
<span class="hljs-title function_">setIsLoading</span>(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> http.<span class="hljs-title function_">post</span>(<span class="hljs-string">/auth/login</span>, formData);
<span class="hljs-keyword">await</span> <span class="hljs-title function_">storeToken</span>(resp.<span class="hljs-property">data</span>);
router.<span class="hljs-title function_">push</span>(<span class="hljs-string">"/dashboard"</span>);
<span class="hljs-title function_">toast</span>({
<span class="hljs-attr">title</span>: <span class="hljs-string">"Login Successful"</span>,
});
} <span class="hljs-keyword">catch</span> (error) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">"error logging in"</span>, error);
} <span class="hljs-keyword">finally</span> {
<span class="hljs-title function_">setIsLoading</span>(<span class="hljs-literal">false</span>);
}
}</pre></div><p id="173a">After testing this check your cookie storage in devtools and the token should be set:</p><figure id="de3a"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*VxsyZ7KTXg_XygebpIfq1Q.png"><figcaption></figcaption></figure><p id="7f8d">Now let’s move on to using the token to communicate with our API.</p><h1 id="ad3b">2. Accessing the Token</h1><p id="3425">So accessing the token is different depending on the context. If you are accessing it on the server it looks like:</p><div id="ba24"><pre>import {cookies} <span class="hljs-keyword">from</span> <span class="hljs-string">"next/headers"</span>;
<span class="hljs-keyword">const</span> authToken = cookies().<span class="hljs-keyword">get</span>(<span class="hljs-string">"accessToken"</span>)?.<span class="hljs-keyword">value</span> </pre></div><p id="636a">In the Client side, because we set <code>httpOnly: true</code> . How do we now access our jwt? That’s where creating an api route comes in. It would have been really cool to have a server action for retrieving the token but we don’t have that yet. “fix up Next.js team”!</p><p id="dc9e">Docs for api routes using the app router <a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">here</a></p><p id="c93d">so the gist is a folder structure of <code>app/api/auth/token/route.ts</code> resolves to an endpoint with path of <code>/api/auth/token</code> . Now in the route.ts file we write:</p><div id="916b"><pre>import { cookies } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/headers'</span>
export async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">request: Request</span>) </span>{
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">authToken</span> = <span class="hljs-title function_ invoke__">cookies</span>().<span class="hljs-title function_ invoke__">get</span>(<span class="hljs-string">'accessToken'</span>)?.value
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">headers</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Headers</span>();
headers.<span class="hljs-title function_ invoke__">append</span>(<span class="hljs-string">"Authorization"</span>, authToken);
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">response</span> = await <span class="hljs-title function_ invoke__">fetch</span>(`${process.env.NEXT_PUBLIC_API_URL}/user`,{
<span class="hljs-attr">headers</span>: headers
}
<span class="hljs-keyword">if</span> (response.status === <span class="hljs-number">401</span>) {
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">refreshPayload</span> = {
<span class="hljs-string">"refresh_token"</span>: <span class="hljs-title function_ invoke__">cookies</span>().<span class="hljs-title function_ invoke__">get</span>(<span class="hljs-string">'refreshToken'</span>)?.value
}
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">res</span> = await <span class="hljs-title function_ invoke__">fetch</span>(`${process.env.NEXT_PUBLIC_API_URL}/refresh-token, {
<span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
<span class="hljs-attr">headers</span>: {
<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
},
<span class="hljs-attr">body</span>: JSON.<span class="hljs-title function_ invoke__">stringify</span>(refreshPayload),
}
<span class="hljs-keyword">const</span> jsonData = await res.<span class="hljs-title function_ invoke__">json</span>()
<span class="hljs-title function_ invoke__">cookies</span>().<span class="hljs-title function_ invoke__">set</span>({
<span class="hljs-attr">name</span>: <span class="hljs-string">"acce
Options
ssToken"</span>,
<span class="hljs-attr">value</span>: jsonData.token,
<span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
<span class="hljs-attr">sameSite</span>: <span class="hljs-string">"strict"</span>,
<span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
})
<span class="hljs-title function_ invoke__">cookies</span>().<span class="hljs-title function_ invoke__">set</span>({
<span class="hljs-attr">name</span>: <span class="hljs-string">"refreshToken"</span>,
<span class="hljs-attr">value</span>: jsonData.refresh_token,
<span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
<span class="hljs-attr">sameSite</span>: <span class="hljs-string">"strict"</span>,
<span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
})
}
<span class="hljs-keyword">const</span> resData = {
<span class="hljs-attr">token</span>: <span class="hljs-title function_ invoke__">cookies</span>().<span class="hljs-title function_ invoke__">get</span>(<span class="hljs-string">'accessToken'</span>)?.value
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(JSON.<span class="hljs-title function_ invoke__">stringify</span>(resData), {
<span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
<span class="hljs-attr">headers</span>: {
<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
},
})
}</pre></div><p id="66db">Now let’s break down what this api route does:</p><ul><li>we retrieve the jwt from the cookie store</li><li>we make a request to /user on our api to check if the token is still valid. /user can be replaced with any protected route on your api</li><li>if we receive the <code>unauthorized(401)</code> error code we send a request to get a new token pair with our refresh token.</li><li>then we return the valid token back to the client for use.</li></ul><p id="260f">Now, to make use of this API route we can write an axios interceptor like so:</p><div id="b457"><pre>axiosInstance.<span class="hljs-property">interceptors</span>.<span class="hljs-property">request</span>.<span class="hljs-title function_">use</span>(<span class="hljs-keyword">async</span> (config) => {
<span class="hljs-keyword">if</span> (config.<span class="hljs-property">url</span>?.<span class="hljs-title function_">includes</span>(<span class="hljs-string">"auth"</span>)) {
<span class="hljs-keyword">return</span> config
}
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">'/api/auth/token'</span>)
<span class="hljs-keyword">const</span> resData = <span class="hljs-keyword">await</span> res.<span class="hljs-title function_">json</span>()
<span class="hljs-keyword">const</span> token = resData?.<span class="hljs-property">token</span>
config.<span class="hljs-property">headers</span>![<span class="hljs-string">'Authorization'</span>] = <span class="hljs-string">"Bearer "</span> + token
<span class="hljs-keyword">return</span> config
},
<span class="hljs-function">(<span class="hljs-params">error</span>) =></span> {
<span class="hljs-keyword">return</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">reject</span>(error)
}
)</pre></div><p id="8f27">What this interceptor does is: “if the app is making a request to an auth route like <code>/auth/login</code> , <code>/auth/signup</code> no token is sent along with the request but if it is to a protected route it fetches the access token from our api route and passes it in the <code>Authorization</code> header.</p><h2 id="c7b2">Further reading:</h2><p id="fb63">This article was greatly inspired by:</p><div id="b5f3" class="link-block">
<a href="https://javascript.plainenglish.io/next-js-secure-authentication-using-http-only-cookie-graphql-or-rest-a4ef94cec9e8">
<div>
<div>
<h2>Next.js Secure Authentication Using http-only Cookie (GraphQL or REST)</h2>
<div><h3>When it comes to user authentication we need to make sure our application is secured from any potential threats. To…</h3></div>
<div><p>javascript.plainenglish.io</p></div>
</div>
<div>
<div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*FBBZ5ksEs057sEfEkcl42g.png)"></div>
</div>
</div>
</a>
</div><p id="7edc">If you want to see how to do this with graphql. or you are working on older versions of Next without server actions, I suggest the article.</p><p id="948d">Thanks for reading and if you have any questions, drop them in the comments.</p><p id="50be">Happy building!🚀</p><h1 id="d403">Stackademic</h1><p id="3a1b"><i>Thank you for reading until the end. Before you go:</i></p><ul><li><i>Please consider <b>clapping</b> and <b>following</b> the writer! 👏</i></li><li><i>Follow us on <a href="https://twitter.com/stackademichq"><b>Twitter(X)</b></a>, <a href="https://www.linkedin.com/company/stackademic"><b>LinkedIn</b></a>, and <a href="https://www.youtube.com/c/stackademic"><b>YouTube</b></a><b>.</b></i></li><li><i>Visit <a href="http://stackademic.com/"><b>Stackademic.com</b></a> to find out more about how we are democratizing free programming education around the world.</i></li></ul></article></body>