Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cookie): cookie flags support and minor fixes #7

Merged
merged 4 commits into from Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
@@ -0,0 +1,5 @@
NUXT_OPENID_CONNECT_OP_ISSUER=""
NUXT_OPENID_CONNECT_OP_CLIENT_ID=""
NUXT_OPENID_CONNECT_OP_CLIENT_SECRET=""
NUXT_OPENID_CONNECT_OP_CALLBACK_URL="
NUXT_OPENID_CONNECT_CONFIG_COOKIE_FLAGS_ACCESS_TOKEN_SECURE="false"
33 changes: 31 additions & 2 deletions README.md
Expand Up @@ -27,9 +27,32 @@ yarn add nuxt-openid-connect
- Then, add `nuxt-openid-connect` module to nuxt.config.ts and change to your configs (`openidConnect`):
```ts
export default defineNuxtConfig({
// runtime config for nuxt-openid-connect example -- you can use env variables see .env.example
runtimeConfig: {
openidConnect: {
op: {
issuer: '',
clientId: '',
clientSecret: '',
callbackUrl: '',
},
config: {
cookieFlags: {
access_token: {
httpOnly: true,
secure: false,
}
}
}
},
},

// add nuxt-openid-connect module here...
modules: [
'nuxt-openid-connect'
],

// configuration for nuxt-openid-connect
openidConnect: {
addPlugin: true,
op: {
Expand All @@ -44,15 +67,21 @@ export default defineNuxtConfig({
]
},
config: {
response_types: 'id_token',
response_type: 'id_token', // or 'code'
secret: 'oidc._sessionid',
cookie: { loginName: '' },
cookiePrefix: 'oidc._',
cookieEncrypt: true,
cookieEncryptKey: 'bfnuxt9c2470cb477d907b1e0917oidc', // 32
cookieEncryptIV: 'ab83667c72eec9e4', // 16
cookieEncryptALGO: 'aes-256-cbc',
cookieMaxAge: 24 * 60 * 60 // default one day
cookieMaxAge: 24 * 60 * 60, // default one day
cookieFlags: { // default is empty
access_token: {
httpOnly: true,
secure: false,
}
}
}
}
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -30,6 +30,7 @@
"prepack": "nuxt-module-build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:preview": "nuxi preview playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"pub": "npm publish --access public"
},
Expand Down
35 changes: 30 additions & 5 deletions playground/nuxt.config.ts
@@ -1,4 +1,3 @@
import openidConnect from '..'

export default defineNuxtConfig({
app: {
Expand All @@ -12,9 +11,30 @@ export default defineNuxtConfig({
]
}
},

modules: [
openidConnect
'nuxt-openid-connect'
],

runtimeConfig: {
openidConnect: {
op: {
issuer: '',
clientId: '',
clientSecret: '',
callbackUrl: '',
},
config: {
cookieFlags: {
access_token: {
httpOnly: true,
secure: false,
}
}
}
},
},

openidConnect: {
addPlugin: true,
op: {
Expand All @@ -25,19 +45,24 @@ export default defineNuxtConfig({
scope: [
'email',
'profile',
'address'
]
},
config: {
response_types: 'token id_token',
response_type: 'code',
secret: 'oidc._sessionid',
cookie: { loginName: '' },
cookiePrefix: 'oidc._',
cookieEncrypt: true,
cookieEncryptKey: 'bfnuxt9c2470cb477d907b1e0917oidc',
cookieEncryptIV: 'ab83667c72eec9e4',
cookieEncryptALGO: 'aes-256-cbc',
cookieMaxAge: 24 * 60 * 60 // default one day
cookieMaxAge: 24 * 60 * 60,// default one day
cookieFlags: {
access_token: {
httpOnly: true,
secure: false,
}
}
}
}
})
15 changes: 9 additions & 6 deletions src/module.ts
Expand Up @@ -2,6 +2,7 @@ import { fileURLToPath } from 'url'
import { defineNuxtModule, addPlugin, resolveModule, createResolver } from '@nuxt/kit'
import defu from 'defu'
import { name, version } from '../package.json'
import { CookieSerializeOptions } from 'cookie-es';

export type OidcProvider = {
issuer: string,
Expand All @@ -20,7 +21,10 @@ export type Config = {
cookieEncryptIV: string,
cookieEncryptALGO: string,
cookieMaxAge: number,
response_type: string
response_type: string,
cookieFlags?: {
[key: string]: CookieSerializeOptions,
}
}

export interface ModuleOptions {
Expand All @@ -47,9 +51,7 @@ export default defineNuxtModule<ModuleOptions>({
clientSecret: '',
callbackUrl: '',
scope: [
'email',
'profile',
'address'
'openid',
]
},
// express-session configuration
Expand All @@ -62,10 +64,11 @@ export default defineNuxtModule<ModuleOptions>({
cookieEncryptIV: 'ab83667c72eec9e4',
cookieEncryptALGO: 'aes-256-cbc',
cookieMaxAge: 24 * 60 * 60, // default one day
response_type: 'id_token'
response_type: 'id_token',
cookieFlags: {}
}
},
setup (options, nuxt) {
setup(options, nuxt) {
// console.log(options.op)
const { resolve } = createResolver(import.meta.url)
const resolveRuntimeModule = (path: string) => resolveModule(path, { paths: resolve('./runtime') })
Expand Down
14 changes: 7 additions & 7 deletions src/runtime/plugin.ts
Expand Up @@ -14,7 +14,7 @@ class Oidc {
private $useState: any // State: Nuxt.useState (share state in all nuxt pages and components) https://v3.nuxtjs.org/guide/features/state-management
public $storage: Storage // LocalStorage: Browser.localStorage (share state in all sites, use in page refresh.)

constructor () {
constructor() {
this.state = { user: {}, isLoggedIn: false }

this.$useState = useState<UseState>('useState', () => { return { user: {}, isLoggedIn: false } })
Expand All @@ -29,7 +29,7 @@ class Oidc {
this.$storage = storage
}

get user () {
get user() {
const userInfoState = this.$useState.value.user
const userInfoLS = this.$storage.getUserInfo()
if ((isUnset(userInfoState))) {
Expand All @@ -42,14 +42,14 @@ class Oidc {
// return this.state.user // not auto update
}

get isLoggedIn () {
get isLoggedIn() {
const isLoggedIn = this.$useState.value.isLoggedIn
const isLoggedInLS = this.$storage.isLoggedIn()
// console.log('isLoggedIn', isLoggedIn, isLoggedInLS)
return isLoggedIn || isLoggedInLS
}

setUser (user: any) {
setUser(user: any) {
this.state.user = user
this.state.isLoggedIn = Object.keys(user).length > 0

Expand All @@ -59,7 +59,7 @@ class Oidc {
this.$storage.setUserInfo(user)
}

async fetchUser () {
async fetchUser() {
try {
if (process.server) {
console.log('serve-render: fetchUser from cookie.')
Expand Down Expand Up @@ -89,7 +89,7 @@ class Oidc {
}
}

login (redirect = '/') {
login(redirect = '/') {
if (process.client) {
const params = new URLSearchParams({ redirect })
const toStr = '/oidc/login' // + params.toString()
Expand All @@ -98,7 +98,7 @@ class Oidc {
}
}

logout () {
logout() {
// TODO clear user info when accessToken expired.
if (process.client) {
this.$useState.value.user = {}
Expand Down
10 changes: 6 additions & 4 deletions src/runtime/server/routes/oidc/callback.ts
Expand Up @@ -26,22 +26,24 @@ export default defineEventHandler(async (event) => {
res.writeHead(302, { Location: '/' })
res.end()

async function getUserInfo (accessToken: string) {
async function getUserInfo(accessToken: string) {
try {
const userinfo = await issueClient.userinfo(accessToken)
setCookie(event, config.cookiePrefix + 'access_token', accessToken, {
maxAge: config.cookieMaxAge
maxAge: config.cookieMaxAge,
...config.cookieFlags['access_token' as keyof typeof config.cookieFlags]
})
const cookie = config.cookie
for (const [key, value] of Object.entries(userinfo)) {
if (cookie && Object.prototype.hasOwnProperty.call(cookie, key)) {
setCookie(event, config.cookiePrefix + key, JSON.stringify(value), {
maxAge: config.cookieMaxAge
maxAge: config.cookieMaxAge,
...config.cookieFlags[key as keyof typeof config.cookieFlags]
})
}
}
const encryptedText = await encrypt(JSON.stringify(userinfo), config)
setCookie(event, config.cookiePrefix + 'user_info', encryptedText)
setCookie(event, config.cookiePrefix + 'user_info', encryptedText, { ...config.cookieFlags['user_info' as keyof typeof config.cookieFlags] })
} catch (err) {
console.log(err)
}
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/server/routes/oidc/login.ts
Expand Up @@ -25,15 +25,16 @@ export default defineEventHandler(async (event) => {
redirect_uri: callbackUrl,
response_type: config.response_type,
nonce: sessionid,
scope: ['openid'].concat(op.scope).join(' ') // 'openid'
scope: op.scope.join(' ') // 'openid' will be added by default
}
const authUrl = issueClient.authorizationUrl(parameters)
// console.log(authUrl)

console.log(sessionid)
if (sessionid) {
setCookie(event, sessionkey, sessionid, {
maxAge: config.cookieMaxAge
maxAge: config.cookieMaxAge,
...config.cookieFlags[sessionkey as keyof typeof config.cookieFlags]
})
}

Expand Down
6 changes: 3 additions & 3 deletions src/runtime/server/routes/oidc/user.ts
Expand Up @@ -13,8 +13,8 @@ export default defineEventHandler(async (event) => {
const userinfoCookie = getCookie(event, config.cookiePrefix + 'user_info')

if (userinfoCookie) {
const userInfoStr = await decrypt(userinfoCookie, config)
return JSON.parse(userInfoStr)
const userInfoStr: string | undefined = await decrypt(userinfoCookie, config)
return JSON.parse(userInfoStr ?? '')
} else if (accesstoken) {
try {
// load user info from oidc server.
Expand All @@ -24,7 +24,7 @@ export default defineEventHandler(async (event) => {
// add encrypted userinfo to cookies.
try {
const encryptedText = await encrypt(JSON.stringify(userinfo), config)
setCookie(event, config.cookiePrefix + 'user_info', encryptedText)
setCookie(event, config.cookiePrefix + 'user_info', encryptedText, { ...config.cookieFlags['user_info' as keyof typeof config.cookieFlags] })
} catch (err) {
console.error('encrypted userinfo error.', err)
}
Expand Down