Secbone / koa-session2

Middleware for Koa2 to get/set session
MIT License
152 stars 30 forks source link

How can I store some info in session? JWT token for instance #46

Closed Blackening999 closed 6 years ago

Blackening999 commented 6 years ago

Hello,

interesting lib. With better docs in comparison with koa/session. I'm struggling to share a context while working with graphQL app. There, I have to set a token after performing login mutation (based on user data). I wasn't able to do so with koa/session as the only property I could change was maxAge

Secbone commented 6 years ago

@Blackening999 I don't fully understand the problem because that I don't know that's the JWT token look like. In my case, you can store some info like this

let userInfo = {
    name: 'tom',
    age: 26,
}
let token = 'abcd1234'

ctx.session.user = userInfo
ctx.session.token = token

Hope this can help you~

Blackening999 commented 6 years ago

Unfortunately, it's not working :\

Here's my case:

server.js

import Koa from 'koa'
import KoaRouter from 'koa-router'
import cors from '@koa/cors'
import compress from 'koa-compress'
import koaSession from 'koa-session2'
import graphqlHTTP from 'koa-graphql'
import webpackMiddleware from 'koa-webpack'

import chalk from 'chalk'

import sessionService from './services/session'
import Store from './redisStore'
import webpackConfig from '../../webpack.config.dev'
import graphqlSchema from './schema'

// const ENV = process.env.NODE_ENV || 'development'
const PORT = process.env.PORT || 3000
// const isDev = ENV === 'development'

const graphQLServer = new Koa()

const router = new KoaRouter().get('/ping', async ctx => {
  ctx.body = 'pong'
})

router.get('*', ctx => {
  ctx.body = `<!DOCTYPE html>
                    <html>
                      <head>
                        <meta charset="utf-8">
                        <meta name="viewport" content="width=device-width, initial-scale=1">
                        <title>Create relay modern app</title>
                      </head>
                      <body>
                        <div id="reactDiv" />
                        <script type="application/javascript" src="/build/bundle.js"></script>
                      </body>
                    </html>`
})

graphQLServer.use(
  koaSession({
    store: new Store(),
    key: 'sssecret',
  }),
)
graphQLServer.use(sessionService)

router.all(
  '/graphql',
  graphqlHTTP((request, _, ctx) => ({
    schema: graphqlSchema,
    graphiql: true,
    rootValue: { ctx },
    pretty: true,
  })),
)

graphQLServer.use(cors())
graphQLServer.use(compress())

graphQLServer.use(
  webpackMiddleware({
    config: webpackConfig,
    hot: true,
  }),
)

graphQLServer.use(router.routes()).use(router.allowedMethods())

graphQLServer.listen(PORT, () => {
  console.log(`${chalk.magenta('\n[INFO]')} App listening on port ${PORT}!`)
})

store.js

import Redis from 'ioredis'
import { Store } from 'koa-session2'

class RedisStore extends Store {
  constructor() {
    super()
    this.redis = new Redis()
  }

  async get(sid) {// ctx
    const data = await this.redis.get(`SESSION:${sid}`)
    return JSON.parse(data)
  }

  async set(session, { sid = this.getID(24), maxAge = 630000000 } = {}) { // ctx
    try {
      // Use redis set EX to automatically drop expired sessions
      await this.redis.set(
        `SESSION:${sid}`,
        JSON.stringify(session),
        'EX', maxAge / 1000
      )
    } catch (e) {
      console.log(e)
    }

    return sid
  }

  async destroy(sid) {// ctx
    return await this.redis.del(`SESSION:${sid}`)
  }
}

export default RedisStore

session.js

import { decodeToken } from '../../lib/authentication'

async function loadSessionData(ctx) {
  if (ctx.session && ctx.session.token) {
    return new Promise(resolve => {
      let tokenData = null
      let err = null

      try {
        tokenData = decodeToken(ctx.session.token)
      } catch (error) {
        // eslint-disable-next-line no-undef
        err = error
        console.log(err)
      }

      resolve([err, tokenData])
    })
  }

  return new Promise(resolve => resolve([null, null]))
}

export default async function sessionMiddleware(ctx, next) {
  console.log(`session: ${ctx.session}`)
  console.log(`session: ${JSON.stringify(ctx.session)}`) // nothing here

  const [err, sessionData] = await loadSessionData(ctx)

  if (err) {
    console.log(`err: ${err}`)
    ctx.status = 400
    ctx.body = 'no token'
    await next()
  } else if (sessionData) {
    ctx.tokenData = sessionData || {}
    await next()
  } else {
    await next()
  }
}

LoginMutation.js (where I actually attempt to record something to session)

import { GraphQLNonNull, GraphQLString } from 'graphql'
import { mutationWithClientMutationId } from 'graphql-relay'

// TODO: work on login
import { createToken, decodeToken } from '../../../lib/authentication'
import { getUserWithCredentials } from '../../services/user'
import UserType from '../type/UserType'

export default mutationWithClientMutationId({
  name: 'Login',
  inputFields: {
    email: {
      type: new GraphQLNonNull(GraphQLString),
    },
    password: {
      type: new GraphQLNonNull(GraphQLString),
    },
  },
  outputFields: {
    user: {
      type: UserType,
      resolve: ({ user }) => user,
    },
  },
  mutateAndGetPayload: async ({ email, password }, _, { rootValue: { ctx } }) => {
    const user = await getUserWithCredentials(email, password)

    // set session token so that user is authenticated on next requests via a cookie
    if (user) {
      /* eslint-disable no-param-reassign */
      const token = createToken(user)
      ctx.session.token = token
      ctx.tokenData = decodeToken(token)
      console.log(`${JSON.stringify(ctx.session)}`) // it's here!
      /* eslint-enable no-param-reassign */
    }
    return { user }
  },
})
Secbone commented 6 years ago

@Blackening999 Could you check if koa has received cookies by this code ctx. cookies.get('sssecret') ?

Blackening999 commented 6 years ago

The issue were because of lack of "credentials: "same-origin" header