Skip to content

Commit

Permalink
feat: 🎉 initial commits
Browse files Browse the repository at this point in the history
  • Loading branch information
naupaw committed Jul 6, 2020
0 parents commit 0a06e40
Show file tree
Hide file tree
Showing 9 changed files with 5,970 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.next
yarn-error.log
.DS_Store
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Nextjs CSRF POC (Proof of Concept)

According to [wikipedia](https://en.wikipedia.org/wiki/Cross-site_request_forgery)

> Cross-site request forgery, also known as one-click attack or session riding and abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious exploit of a website where unauthorized commands are transmitted from a user that the web application trusts.
Just a bare minimal implementation using csrf token with [nextjs](https://nextjs.org/)

Module i used

- https://github.com/pillarjs/csrf
- https://github.com/maticzav/nookies
- https://github.com/hoangvvo/next-connect

There are some rules in this case

- Csrf secret stored in `_csrf` cookie.
- Csrf token stored in `x-xsrf-token` cookie, _latter to be used for XHR/API call_.
- In this case i will use [axios](https://github.com/axios/axios) for calling api since the module has built action for carries `x-xsrf-token` automatically
- Restriction only applied on `/api/*` path
- Csrf token also available in `req.token` if you decide to put the token into `pageProps` by using `getServerSideProps`
2 changes: 2 additions & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "next-csrf",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "next dev",
"start": "next start",
"build": "next build"
},
"dependencies": {
"axios": "^0.19.2",
"csrf": "^3.1.0",
"next": "^9.4.4",
"next-connect": "^0.8.1",
"nookies": "^2.3.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.2",
"@types/csrf": "^1.3.2",
"@types/node": "^14.0.14",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"typescript": "^3.9.6"
}
}
6 changes: 6 additions & 0 deletions pages/api/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import withMiddleware from '../../util/withMiddleware'

export default async (req, res) => {
await withMiddleware(req, res)
return res.json({ message: '@pedox' })
}
46 changes: 46 additions & 0 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import axios from 'axios'
import { GetServerSideProps } from 'next'
import { useEffect, useState } from 'react'
import withMiddleware from '../util/withMiddleware'
export default ({ token }) => {
const [name, setName] = useState('')
useEffect(() => {
const callApi = async () => {
const { data } = await axios.get('/api/hello')
setName(data.message)
}
callApi()
}, [setName])

return (
<div className='main'>
<h1>hello {name}</h1>
<p>
Direct access without x-xsrf-token header{' '}
<a href='/api/hello' target='_blank' rel='noopener noreferrer'>
/api/hello
</a>
</p>
<pre>
Check devtools for see request payload{'\n\n'}
In case you want render csrf token to html page {'\n\n'}CSRF TOKEN:{' '}
{token}
</pre>
<style jsx>{`
.main {
font-family: sans-serif;
}
`}</style>
</div>
)
}

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
try {
await withMiddleware(req, res)
return { props: { token: (req as any).token } }
} catch (e) {
//...
}
return { props: { statusCode: 500 } }
}
29 changes: 29 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": [
"node_modules"
],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
]
}
30 changes: 30 additions & 0 deletions util/withMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Tokens from 'csrf'
import nc from 'next-connect'
import { parseCookies } from 'nookies'

const tokens = new Tokens()

const withMiddleware = async (req, res) => {
const handler = nc().use((req, res, next) => {
const cookies = parseCookies({ req })

if (new RegExp('^/api', 'i').test(req.url)) {
if (!tokens.verify(cookies._csrf || '', req.headers['x-xsrf-token'])) {
return res.status(403).json({ message: 'csrf_token_invalid' })
}
}

const secret = cookies._csrf || tokens.secretSync()

req.token = tokens.create(secret)
res.setHeader('Set-Cookie', [
`_csrf=${secret}; path=/`,
`XSRF-TOKEN=${tokens.create(secret)}; path=/`,
])
next()
})

return await handler.apply(req, res)
}

export default withMiddleware
Loading

0 comments on commit 0a06e40

Please sign in to comment.