Bootstrap

okta使用_如何使用Okta向您的Vue应用添加身份验证

okta使用

本文最初发布在Okta开发人员博客上 感谢您支持使SitePoint成为可能的合作伙伴。

从jQuery开始,到Angular,我已经对JavaScript框架改组已经进行了很多年。 在对Angular的复杂性感到沮丧之后,我找到了React,并以为我很清楚。 表面上看似简单的事情最终变成了令人沮丧的混乱。 然后我找到了Vue.js。 感觉还不错。 它按预期工作。 很快 该文档令人难以置信。 模板雄辩。 关于如何处理状态管理,条件渲染,双向绑定,路由等等,存在一致的共识。

本教程将逐步指导您完成Vue.js项目,将安全认证转移到Okta的OpenID Connect API(OIDC) ,锁定受保护的路由以及通过后端REST API服务器执行CRUD操作的步骤。 本教程使用以下技术,但不需要任何专业知识:

关于Vue.js

Vue.js是一个健壮但简单的Javascript框架。 它是所有现代框架进入的最低障碍之一,同时为高性能Web应用程序提供了所有必需的功能。

Vue.js主页

本教程涵盖两个主要构建,一个前端Web应用程序和一个后端REST API服务器。 前端将是一个具有主页,登录和注销以及帖子管理器的单页面应用程序(SPA)。

Okta的OpenID Connect(OIDC)将通过使用Okta的Vue SDK处理我们的Web应用程序的身份验证。 如果未经身份验证的用户导航到帖子管理器,则Web应用程序应尝试对用户进行身份验证。

该服务器将运行带有SequelizeEpilogue的 Express 。 在较高的层次上,使用Sequelize和Epilogue,您只需几行代码即可快速生成动态REST端点。

从Web应用程序和Express中间件中的Okta的JWT验证程序发出请求以验证令牌时,将使用基于JWT的身份验证。 您的应用程序将公开以下端点,所有端点均要求请求具有有效的访问令牌。

- GET /posts
- GET /posts/:id
- POST /posts
- PUT /posts/:id
- DELETE /posts/:id

创建您的Vue.js应用

为了快速启动您的项目,您可以利用vue-cli的脚手架功能。 在本教程中,您将使用渐进式Web应用程序(PWA)模板 ,其中包括一些功能,包括webpack热重载 ,CSS提取和单元测试。

如果您不熟悉PWA的宗旨,请查看我们关于渐进式Web应用程序的终极指南

要安装vue-cli运行:

npm install -g vue-cli

接下来,您需要初始化您的项目。 运行vue init命令时,只需接受所有默认值即可。

vue init pwa my-vue-app
cd ./my-vue-app
npm install
npm run dev

将您喜欢的浏览器指向http://localhost:8080 ,您应该看到您的工作成果:

欢迎使用您的Vue.js PWA

额外的信用 :查看适用于vue-cli的其他模板

安装Bootstrap

让我们安装bootstrap-vue,以便您可以利用各种预制组件 (此外,您可以专注于功能而不是自定义CSS):

npm i --save bootstrap-vue bootstrap

要完成安装,请修改./src/main.js以包含bootstrap-vue并导入所需CSS文件。 您的./src/main.js文件应如下所示:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

使用Okta添加身份验证

在Web应用程序中处理身份验证是每个开发人员生存的祸根。 这就是Okta用最少的代码保护您的Web应用程序安全的地方。 首先,您需要在Okta中创建一个OIDC应用程序。 注册一个永久免费的开发者帐户 (如果已有,请登录)。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

Okta开发人员注册

登录后,单击“添加应用程序”创建一个新的应用程序。

添加申请

选择“单页应用”平台选项。

新的应用程序选项

默认的应用程序设置应与图片相同。

Okta应用程序设置

要安装Okta Vue SDK,请运行以下命令:

npm i --save @okta/okta-vue

打开./src/router/index.js并将整个文件替换为以下代码。

import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
import PostsManager from '@/components/PostsManager'
import Auth from '@okta/okta-vue'

Vue.use(Auth, {
  issuer: 'https://{yourOktaDomain}.com/oauth2/default',
  client_id: '{yourClientId}',
  redirect_uri: 'http://localhost:8080/implicit/callback',
  scope: 'openid profile email'
})

Vue.use(Router)

let router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    },
    {
      path: '/implicit/callback',
      component: Auth.handleCallback()
    },
    {
      path: '/posts-manager',
      name: 'PostsManager',
      component: PostsManager,
      meta: {
        requiresAuth: true
      }
    }
  ]
})

router.beforeEach(Vue.prototype.$auth.authRedirectGuard())

export default router

您需要替换{yourOktaDomain}{yourClientId} ,它们可以在Okta开发者控制台的应用程序概述页面上找到。 这会将authClient对象注入到Vue实例中,可以通过在Vue实例内的任何位置调用this.$auth来访问它。

Vue.use(Auth, {
  issuer: 'https://{yourOktaDomain}.com/oauth2/default',
  client_id: '{yourClientId}',
  redirect_uri: 'http://localhost:8080/implicit/callback',
  scope: 'openid profile email'
})

Okta身份验证流程的最后一步是使用URL中的令牌值将用户重定向回您的应用程序。 SDK中包含的Auth.handleCallback()组件可处理重定向并在浏览器中保留令牌。

{
  path: '/implicit/callback',
  component: Auth.handleCallback()
}

您还需要锁定受保护的路由,以防止未经身份验证的用户访问。 这可以通过实现导航卫士来实现。 顾名思义, 导航卫士主要用于通过重定向或取消来保护导航。

该SDK自带的方法auth.authRedirectGuard()支票匹配路由的元数据的键requiresAuth并且如果它们不认证的用户重定向至身份验证流程。

router.beforeEach(Vue.prototype.$auth.authRedirectGuard())

安装此导航保护后,具有以下元数据的任何路线都将受到保护。

meta: {
  requiresAuth: true
}

在Vue中自定义您的应用布局

Web应用程序的布局位于组件./src/App.vue 。 您可以使用router-view组件来呈现给定路径的匹配组件。

对于主菜单,您将需要根据activeUser的状态更改某些菜单项的可见性:

  • 未验证:仅显示登录
  • 已验证:仅显示注销

您可以使用Vue.js中的v-if指令切换这些菜单项的可见性,该指令检查组件上是否存在activeUser 。 当组件加载时(调用created() )或路由更改时,我们要刷新activeUser

打开./src/App.vue并复制/粘贴以下代码。

<template>
  <div id="app">
    <b-navbar toggleable="md" type="dark" variant="dark">
      <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
      <b-navbar-brand to="/">My Vue App</b-navbar-brand>
      <b-collapse is-nav id="nav_collapse">
        <b-navbar-nav>
          <b-nav-item to="/">Home</b-nav-item>
          <b-nav-item to="/posts-manager">Posts Manager</b-nav-item>
          <b-nav-item href="#" @click.prevent="login" v-if="!activeUser">Login</b-nav-item>
          <b-nav-item href="#" @click.prevent="logout" v-else>Logout</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <!-- routes will be rendered here -->
    <router-view />
  </div>
</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      activeUser: null
    }
  },
  async created () {
    await this.refreshActiveUser()
  },
  watch: {
    // everytime a route is changed refresh the activeUser
    '$route': 'refreshActiveUser'
  },
  methods: {
    login () {
      this.$auth.loginRedirect()
    },
    async refreshActiveUser () {
      this.activeUser = await this.$auth.getUser()
    },
    async logout () {
      await this.$auth.logout()
      await this.refreshActiveUser()
      this.$router.push('/')
    }
  }
}
</script>

每次登录都必须注销。 以下代码段将注销您的用户,刷新活动用户(现在为null),然后将用户重定向到首页。 当用户单击导航中的注销链接时,将调用此方法。

async logout () {
  await this.$auth.logout()
  await this.refreshActiveUser()
  this.$router.push('/')
}

组件是Vue.js中的构建块。 您的每个页面都将在应用程序中定义为组件。 由于vue-cli Webpack模板使用vue-loader ,因此组件源文件具有将模板,脚本和样式分开的约定( 请参见此处 )。

现在,您已经添加了vue-bootstrap,修改./src/components/Hello.vue以删除vue-cli生成的样板链接。

<template>
  <div class="hero">
    <div>
      <h1 class="display-3">Hello World</h1>
      <p class="lead">This is the homepage of your vue app</p>
    </div>
  </div>
</template>

<style>
  .hero {
    height: 90vh;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
  }
  .hero .lead {
    font-weight: 200;
    font-size: 1.5rem;
  }
</style>

此时,您可以将“邮递管理器”页面存根,以测试身份验证流程。 确认身份验证成功后,您将开始构建在Posts模型上执行CRUD操作所需的API调用和组件。

创建一个新文件./src/components/PostsManager.vue并粘贴以下代码:

<template>
  <div class="container-fluid mt-4">
    <h1 class="h1">Posts Manager</h1>
    <p>Only authenticated users should see this page</p>
  </div>
</template>

使用您的Vue.js前端和身份验证流程进行测试

在终端中运行npm run dev (如果尚未运行)。 导航到http://localhost:8080 ,您应该看到新的主页。

你好,世界

如果单击Posts ManagerLogin ,则应该直接转到Okta的流程。 输入您的Okta开发人员帐户凭据。

注意:如果您登录到Okta开发者帐户,您将被自动重定向回该应用程序。 您可以使用隐身或私人浏览模式对此进行测试。

Okta登录

如果成功,您应该返回登录的主页。

登录后的首页

单击Posts Manager链接应呈现受保护的组件。

职位经理

添加后端REST API服务器

现在,用户可以安全地进行身份验证,您可以构建REST API服务器以在帖子模型上执行CRUD操作。 将以下依赖项添加到您的项目中:

npm i --save express cors @okta/jwt-verifier sequelize sqlite3 epilogue axios

然后,创建文件./src/server.js并粘贴以下代码。

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const Sequelize = require('sequelize')
const epilogue = require('epilogue')
const OktaJwtVerifier = require('@okta/jwt-verifier')

const oktaJwtVerifier = new OktaJwtVerifier({
  clientId: '{yourClientId}',
  issuer: 'https://{yourOktaDomain}.com/oauth2/default'
})

let app = express()
app.use(cors())
app.use(bodyParser.json())

// verify JWT token middleware
app.use((req, res, next) => {
  // require every request to have an authorization header
  if (!req.headers.authorization) {
    return next(new Error('Authorization header is required'))
  }
  let parts = req.headers.authorization.trim().split(' ')
  let accessToken = parts.pop()
  oktaJwtVerifier.verifyAccessToken(accessToken)
    .then(jwt => {
      req.user = {
        uid: jwt.claims.uid,
        email: jwt.claims.sub
      }
      next()
    })
    .catch(next) // jwt did not verify!
})

// For ease of this tutorial, we are going to use SQLite to limit dependencies
let database = new Sequelize({
  dialect: 'sqlite',
  storage: './test.sqlite'
})

// Define our Post model
// id, createdAt, and updatedAt are added by sequelize automatically
let Post = database.define('posts', {
  title: Sequelize.STRING,
  body: Sequelize.TEXT
})

// Initialize epilogue
epilogue.initialize({
  app: app,
  sequelize: database
})

// Create the dynamic REST resource for our Post model
let userResource = epilogue.resource({
  model: Post,
  endpoints: ['/posts', '/posts/:id']
})

// Resets the database and launches the express app on :8081
database
  .sync({ force: true })
  .then(() => {
    app.listen(8081, () => {
      console.log('listening to port localhost:8081')
    })
  })

确保使用来自Okta的OIDC应用中的值替换上面代码中的变量{yourOktaDomain}{clientId}

添加续集

Sequelize是Node.js的基于承诺的ORM。 它支持方言PostgreSQL,MySQL,SQLite和MSSQL,并具有可靠的事务支持,关系,读取复制等功能。

为了简化本教程,您将使用SQLite限制外部依赖关系。 以下代码使用SQLite作为驱动程序初始化Sequelize实例。

let database = new Sequelize({
  dialect: 'sqlite',
  storage: './test.sqlite'
})

每个帖子都有titlebody 。 (字段createdAt ,和updatedAt由Sequelize自动添加)。 使用Sequelize,您可以通过在实例上调用define()来定义模型。

let Post = database.define('posts', {
  title: Sequelize.STRING,
  body: Sequelize.TEXT
})

添加结尾

Epilogue通过Express应用程序中的Sequelize模型创建灵活的REST端点。 如果您曾经编码过REST端点,您就会知道有多少重复。 干FTW!

// Initialize epilogue
epilogue.initialize({
  app: app,
  sequelize: database
})

// Create the dynamic REST resource for our Post model
let userResource = epilogue.resource({
  model: Post,
  endpoints: ['/posts', '/posts/:id']
})

验证您的JWT

这是REST API服务器中最关键的组件。 没有此中间件,任何用户都可以在我们的数据库上执行CRUD操作。 如果不存在授权头,或者访问令牌无效,则API调用将失败并返回错误。

// verify JWT token middleware
app.use((req, res, next) => {
  // require every request to have an authorization header
  if (!req.headers.authorization) {
    return next(new Error('Authorization header is required'))
  }
  let parts = req.headers.authorization.trim().split(' ')
  let accessToken = parts.pop()
  oktaJwtVerifier.verifyAccessToken(accessToken)
    .then(jwt => {
      req.user = {
        uid: jwt.claims.uid,
        email: jwt.claims.sub
      }
      next()
    })
    .catch(next) // jwt did not verify!
})

运行服务器

打开一个新的终端窗口,并使用命令node ./src/server运行服务器。 您应该从Sequelize中看到调试信息,并且该应用程序正在侦听端口8081。

完成帖子管理器组件

现在REST API服务器已经完成,您可以开始连接帖子管理器以获取帖子,创建帖子,编辑帖子和删除帖子。

我总是将我的API集成集中到单个帮助程序模块中。 这样可以使组件中的代码更加整洁,并提供单个位置,以防您需要通过API请求进行任何更改。

创建文件./src/api.js并将以下代码复制/粘贴到其中:

import Vue from 'vue'
import axios from 'axios'

const client = axios.create({
  baseURL: 'http://localhost:8081/',
  json: true
})

export default {
  async execute (method, resource, data) {
    // inject the accessToken for each request
    let accessToken = await Vue.prototype.$auth.getAccessToken()
    return client({
      method,
      url: resource,
      data,
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }).then(req => {
      return req.data
    })
  },
  getPosts () {
    return this.execute('get', '/posts')
  },
  getPost (id) {
    return this.execute('get', `/posts/${id}`)
  },
  createPost (data) {
    return this.execute('post', '/posts', data)
  },
  updatePost (id, data) {
    return this.execute('put', `/posts/${id}`, data)
  },
  deletePost (id) {
    return this.execute('delete', `/posts/${id}`)
  }
}

当您使用OIDC进行身份验证时,访问令牌将在浏览器中本地保留。 由于每个API请求都必须具有访问令牌,因此您可以从身份验证客户端获取它,并将其设置在请求中。

let accessToken = await Vue.prototype.$auth.getAccessToken()
return client({
  method,
  url: resource,
  data,
  headers: {
    Authorization: `Bearer ${accessToken}`
  }
})

通过在API帮助器中创建以下代理方法,帮助器模块外部的代码将保持清晰和语义。

getPosts () {
  return this.execute('get', '/posts')
},
getPost (id) {
  return this.execute('get', `/posts/${id}`)
},
createPost (data) {
  return this.execute('post', '/posts', data)
},
updatePost (id, data) {
  return this.execute('put', `/posts/${id}`, data)
},
deletePost (id) {
  return this.execute('delete', `/posts/${id}`)
}

现在,您具有连接帖子管理器组件以通过REST API进行CRUD操作所需的所有组件。 打开./src/components/PostsManager.vue并复制/粘贴以下代码。

<template>
  <div class="container-fluid mt-4">
    <h1 class="h1">Posts Manager</h1>
    <b-alert :show="loading" variant="info">Loading...</b-alert>
    <b-row>
      <b-col>
        <table class="table table-striped">
          <thead>
            <tr>
              <th>ID</th>
              <th>Title</th>
              <th>Updated At</th>
              <th>&nbsp;</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="post in posts" :key="post.id">
              <td>{{ post.id }}</td>
              <td>{{ post.title }}</td>
              <td>{{ post.updatedAt }}</td>
              <td class="text-right">
                <a href="#" @click.prevent="populatePostToEdit(post)">Edit</a> - 
                <a href="#" @click.prevent="deletePost(post.id)">Delete</a>
              </td>
            </tr>
          </tbody>
        </table>
      </b-col>
      <b-col lg="3">
        <b-card :title="(model.id ? 'Edit Post ID#' + model.id : 'New Post')">
          <form @submit.prevent="savePost">
            <b-form-group label="Title">
              <b-form-input type="text" v-model="model.title"></b-form-input>
            </b-form-group>
            <b-form-group label="Body">
              <b-form-textarea rows="4" v-model="model.body"></b-form-textarea>
            </b-form-group>
            <div>
              <b-btn type="submit" variant="success">Save Post</b-btn>
            </div>
          </form>
        </b-card>
      </b-col>
    </b-row>
  </div>
</template>

<script>
import api from '@/api'
export default {
  data () {
    return {
      loading: false,
      posts: [],
      model: {}
    }
  },
  async created () {
    this.refreshPosts()
  },
  methods: {
    async refreshPosts () {
      this.loading = true
      this.posts = await api.getPosts()
      this.loading = false
    },
    async populatePostToEdit (post) {
      this.model = Object.assign({}, post)
    },
    async savePost () {
      if (this.model.id) {
        await api.updatePost(this.model.id, this.model)
      } else {
        await api.createPost(this.model)
      }
      this.model = {} // reset form
      await this.refreshPosts()
    },
    async deletePost (id) {
      if (confirm('Are you sure you want to delete this post?')) {
        // if we are editing a post we deleted, remove it from the form
        if (this.model.id === id) {
          this.model = {}
        }
        await api.deletePost(id)
        await this.refreshPosts()
      }
    }
  }
}
</script>

上市帖子

您将使用api.getPosts()从REST API服务器中获取帖子。 加载组件时以及任何更改操作(创建,更新或删除)之后,您应该刷新帖子列表。

async refreshPosts () {
  this.loading = true
  this.posts = await api.getPosts()
  this.loading = false
}

切换属性this.loading ,以便UI可以反映挂起的API调用。 您可能看不到加载消息,因为API请求没有发送到互联网。

创建帖子

组件中包含一个表格来保存帖子。 提交表单并将其输入绑定到组件上的model对象时,它会连接调用savePosts()

调用savePost() ,它将基于model.id的存在执行更新或创建。 这通常是一种快捷方式,不必定义两个单独的表单即可进行创建和更新。

async savePost () {
  if (this.model.id) {
    await api.updatePost(this.model.id, this.model)
  } else {
    await api.createPost(this.model)
  }
  this.model = {} // reset form
  await this.refreshPosts()
}

更新帖子

更新帖子时,首先必须将帖子加载到表单中。 这将设置model.id ,它将触发savePost()的更新。

async populatePostToEdit (post) {
  this.model = Object.assign({}, post)
}

重要提示: Object.assign()调用复制post参数而不是引用的值。 在Vue中处理对象变异时,应始终将其设置为值,而不是引用。

删除帖子

要删除帖子,只需调用api.deletePost(id) 。 在删除之前进行确认始终是一件好事,因此让我们在本机确认警报框中添加以确保单击是有意的。

async deletePost (id) {
  if (confirm('Are you sure you want to delete this post?')) {
    await api.deletePost(id)
    await this.refreshPosts()
  }
}

测试您的Vue.js + Node CRUD应用

确保服务器和前端都在运行。

1号航站楼

node ./src/server

2号航站楼

npm run dev

导航到http://localhost:8080并进行旋转。

最新帖子

你好世界新邮报

删除帖子

用Vue做更多事!

正如我在这篇文章的开头所说,我认为Vue领先于其他框架。 以下是五个快速原因:

我在本教程中介绍了很多内容,但是如果您第一次不掌握所有内容,也不会感到难过。 您使用这些技术的次数越多,他们就会变得越熟悉。

要了解有关Vue.js的更多信息,请转到https://vuejs.org或从@oktadev团队中查看以下其他重要资源:

您可以在https://github.com/oktadeveloper/okta-vue-node-example上找到本文中开发的应用程序的源代码。

与往常一样,在Twitter上关注@oktadev ,以查看我们的开发团队正在创建的所有精彩内容。

翻译自: https://www.sitepoint.com/add-authentication-vue-okta/

okta使用

;