I } }) store.dispatch('checkLoggedIn')
This file sets up Vue, and serves as the main entry point (or starting point) for the whole JavaScript application.
Next, create
router.js:
import Vue from 'vue' import Router from 'vue-router' import store from './store' import Dashboard from './components/Dashboard.vue' import Login from './components/Login.vue' Vue.use(Router) function requireAuth (to, from, next) { if (!store.state.loggedIn) { next({ path: '/login', query: { redirect: to.path } }) } else { next() } } export default new Router({ mode: 'history', base: __dirname, routes: [ { path: '/', component: Dashboard, beforeEnter: requireAuth }, { path: '/login', component: Login }, { path: '/logout', async beforeEnter (to, from, next) { await store.dispatch('logout') } } ] })
The Vue router keeps track of what page the user is currently viewing, and handles navigating between pages or sections of your app. This file configures the router with three paths (
/,
/login, and
/logout) and associates each path with a Vue component.
You might be wondering what
store,
Dashboard, and
Login"> <h2>{{name}}, here's your to-do list</h2> <input class="new-todo" autofocus <ul class="todo-list"> <todo-item</todo-item> </ul> <p>{{ remaining }} remaining</p> <router-linkLog out</router-link> </div> </template> <script> import TodoItem from './TodoItem' export default { components: { TodoItem }, mounted() { this.$store.dispatch('getAllTodos') }, computed: { name () { return this.$store.state.userName },. (You’ll build the data store in a minute!)> import { mapActions } from 'vuex' export default { props: ['item'], methods: { ...mapActions([ 'toggleTodo', 'deleteTodo' ]) } } < a.
Add another component called
Login.vue:
<template> <div> <h2>Login</h2> <p v- You need to login first. </p> <form @submit. <label for="email">Email</label> <input id="email" v- <label for="password">Password</label> <input id="password" v- <button type="submit">login</button> <p v-{{loginError}}</p> </form> </div> </template> <script> export default { data () { return { email: '', password: '', error: false } }, computed: { loginError () { return this.$store.state.loginError } }, methods: { login () { this.$store.dispatch('login', { email: this.email, password: this.password }) } } } </script> <style scoped> .error { color: red; } label { display: block; } input { display: block; margin-bottom: 10px; } </style>
The Login component renders a simple login form, and shows an error message if the login is unsuccessful.
Notice the
scoped attribute on the
<style> tag? That’s a cool feature called scoped CSS. Marking a block of CSS as
scoped means the CSS rules only apply to this component (otherwise, they apply globally). It’s useful here to set
display: block on the
input and
label elements in this component, without affecting how those elements are rendered elsewhere in the app.
The Dashboard and Login components (and the router configuration) refer to something called
store. I’ll explain what the store is, and how to build it, in the next section.
Before you get there, you need to build one more component. Create a file called
App.vue in the
components folder:
<template> <div class="app-container"> <div class="app-view"> <template v- <router-view></router-view> </template> </div> </div> </template> <script> export default { computed: { loggedIn () { return this.$store.state.loggedIn } } } <; padding: 20px 25px 15px 25px; margin: 30px; position: relative; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 5px 10px 0 rgba(0, 0, 0, 0.1); } </style>
The App component doesn’t do much -- it just provides the base HTML and CSS styles that the other components will be rendered inside. The
<router-view> element loads a component provided by the Vue router, which will render either the Dashboard or Login component depending on the path in the address bar.
If you look back at
boot-app.js, you’ll see this line:
import App from './components/App'
This statement loads the App component, which is then passed to Vue in
new Vue(...).
You’re all done building components! It’s time to add some state management. First, I’ll explain what state management is and why it’s useful.
Add Vuex for state management
Components are a great way to break up your app into manageable pieces, but when you start passing data between many components, it can be, and that’s it. The real meat of Vuex is in mutations and actions, but you’ll write those in separate files to keep everything organized.
Create a file called
mutations.js:
import router from '../router' export const state = { todos: [], loggedIn: false, loginError: null, userName: null } export const mutations = { loggedIn(state, data) { state.loggedIn = true state.userName = (data.name || '').split(' ')[0] || 'Hello' let redirectTo = state.route.query.redirect || '/' router.push(redirectTo) }, loggedOut(state) { state.loggedIn = false router.push('/login') }, loginError(state, message) { state.loginError = message }, loadTodos(state, todos) { state.todos =.
As you can see, this app uses the Vuex store to keep track of both the to-do list (the
state.todos array), and authentication state (whether the user is logged in, what their name is). The Dashboard and Login components access this data with computed properties like:
todos () { return this.$store.state.todos },
The mutations defined here are only half the story, because they only handle updating the state after an action has run. Create another file called
actions.js:
import axios from 'axios' const sleep = ms => { return new Promise(resolve => setTimeout(resolve, ms)) } export const actions = { checkLoggedIn({ commit }) { // Todo: commit('loggedIn') if the user is already logged in }, async login({ dispatch, commit }, data) { // Todo: log the user in commit('loggedIn', { userName: data.email }) }, async logout({ commit }) { // Todo: log the user out commit('loggedOut') }, async loginFailed({ commit }, message) { commit('loginError', message) await sleep(3000) commit('loginError', null) }, async getAllTodos({ commit }) { // Todo: get the user's to-do items commit('loadTodos', [{ text: 'Fake to-do item' }]) }, async addTodo({ dispatch }, data) { // Todo: save a new to-do item await dispatch('getAllTodos') }, async toggleTodo({ dispatch }, data) { // Todo: toggle to-do item completed/not completed await dispatch('getAllTodos') }, async deleteTodo({ dispatch }, id) { // Todo: delete to-do item await dispatch('getAllTodos') } } same mutation with the items returned from the API.
Note: Because the starter template includes Babel and the transform-async-to-generator plugin, the new async/await syntax in ES2017 is available. I love async/await, because it makes dealing with async things like API calls much easier (no more big chains of Promises, or callback hell). As you’ll see in the next section, C# uses the same syntax!
Run the app
You still need to add the backend API and authentication bits, but let’s take a quick break and see what you’ve built so far. Start up the app with
dotnet run and browse to. Log in with a fake username and password:
Tip: If you need to fix bugs or make changes, you don’t need to stop and restart the server with
dotnet run again. As soon as you modify any of your Vue or JavaScript files, the frontend app will be recompiled automatically. Try making a change to the Dashboard component and see it appear instantly in your browser (like magic).
The to-do item is fake, but your app is very real! You’ve set up Vue.js, built components and routing, and added state management with Vuex. The next step is adding the backend API with ASP.NET Core. Grab a refill of coffee and let’s dive in!
Add APIs with ASP.NET Core
The user’s to-do items will be stored in an online database.
Tip:. You’ll modify this file later.
- A pair of controllers in the aptly-named Controllers folder.
In ASP.NET Core, Controllers handle requests to specific routes in your app later, throughout the ASP.NET Core project. }) { let response = await axios.get('/api/todo') if (response && response.data) { let updatedTodos = response.data commit('loadTodos', updatedTodos) } },
The new action code uses the axios HTTP library to make a request to the backend on the
/api/todo route, which will be handled by the
GetAllTodos method on the is still fake, but less fake than before! You’ve successfully connected the backend and frontend and have data flowing between them.
The final step is to add authentication and real data storage to the app. You’re almost there!
Add identity and security with Okta
Okta is a cloud-hosted identity API that makes it easy to add authentication, authorization, and user management to your web and mobile apps. You’ll use it in this project to:
- Add functionality to the Login component
- Require authentication on the backend API
- Store each user’s to-do items securely
To get started, sign up for a free Okta Developer account. After you activate your new account (called an Okta Organization, or Org for short), click Applications at the top of the screen. Choose Single-Page App and change both the base URI need to connect your frontend code to Okta.
Add the Okta Auth SDK
The Okta Auth SDK provides methods that make it easy to authenticate users from JavaScript code. Install it with npm:
npm install @okta/okta-auth-js@1.11.0
Create a file in the ClientApp folder called
oktaAuth.js that holds the Auth SDK configuration and makes the client available to the rest of your Vue app:
import OktaAuth from '@okta/okta-auth-js' const org = '{{yourOktaOrgUrl}}', clientId = '{{appClientId}}', redirectUri = '', authorizationServer = 'default' const oktaAuthClient = new OktaAuth({ url: org, issuer: authorizationServer, clientId, redirectUri }) export default { client: oktaAuthClient }
Replace
{{yourOktaOrgUrl}} with your Okta Org URL, which usually looks like this:. You can find it in the top right corner of the Dashboard page.
Next, paste the Client ID you copied from the application you created a minute ago into the
clientId property.
The
checkLoggedIn,
login, and
logout actions can now be replaced with real implementations in
actions.js:
checkLoggedIn({ commit }) { if (oktaAuth.client.tokenManager.get('access_token')) { let idToken = oktaAuth.client.tokenManager.get('id_token') commit('loggedIn', idToken.claims) } }, async login({ dispatch, commit }, data) { let authResponse try { authResponse = await oktaAuth.client.signIn({ username: data.email, password: data.password }); } catch (err) { let message = err.message || 'Login error' dispatch('loginFailed', message) return } if (authResponse.status !== 'SUCCESS') { console.error("Login unsuccessful, or more info required", response.status) dispatch('loginFailed', 'Login error') return } let tokens try { tokens = await oktaAuth.client.token.getWithoutPrompt({ responseType: ['id_token', 'token'], scopes: ['openid', 'email', 'profile'], sessionToken: authResponse.sessionToken, }) } catch (err) { let message = err.message || 'Login error' dispatch('loginFailed', message) return } // Verify ID token validity try { await oktaAuth.client.token.verify(tokens[0]) } catch (err) { dispatch('loginFailed', 'An error occurred') console.error('id_token failed validation') return } oktaAuth.client.tokenManager.add('id_token', tokens[0]); oktaAuth.client.tokenManager.add('access_token', tokens[1]); commit('loggedIn', tokens[0].claims) }, async logout({ commit }) { oktaAuth.client.tokenManager.clear() await oktaAuth.client.signOut() commit('loggedOut') },
These actions delegate to the Okta Auth SDK, which calls the Okta authentication API to log the user in and get access and ID tokens for the user via OpenID Connect. The Auth SDK also stores and manages the tokens for your app.
You’ll also need to add an
import statement at the top of
actions.js:
import oktaAuth from '../oktaAuth'
Try it out: run the server with
dotnet run and try logging in with the email and password you used to sign up for Okta:
Try logging in, refreshing the page (you should still be logged in!), and logging out.
That takes care of authenticating the user on the frontend. The Vuex store will keep track of the authentication state, and the Okta Auth SDK will handle login, logout, and keeping the user’s tokens fresh.
To secure the backend API, you need to configure ASP.NET Core to use token authentication and require a token when the frontend code makes a request.
Add API token authentication
Under the hood, the Okta Auth SDK uses OpenID Connect to get access and ID tokens when the user logs in. The ID token is used to display the user’s name in the Vue app, and the access token can be used to secure the backend API.
Open up the
Startup.cs file and add this code to the
ConfigureServices method:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "{{yourOktaOrgUrl}}/oauth2/default"; options.Audience = "api://default"; });
This code adds token authentication to the ASP.NET Core authentication system. With this in place, your frontend code will need to attach an access token to requests in order to access the API.
Make sure you replace
{{yourOktaOrgUrl}} with your Okta Org URL (find it in the top-right of your Okta developer console’s Dashboard).
You’ll also]")] [Authorize] // Add this public class TodoController : Controller { // ...
If you tried running the app and looking at your browser’s network console, you’d see a failed API request:
The
TodoController is responding with 401 Unauthorized because it now requires a valid token to access the
/api/todo route, and your frontend code isn’t sending a token.
Open up
actions.js once more and add a small function below
sleep that attaches the user’s token to the HTTP
Authorization header:
const addAuthHeader = () => { return { headers: { 'Authorization': 'Bearer ' + oktaAuth.client.tokenManager.get('access_token').accessToken } } }
Then, update the code that calls the backend in the
getAllTodos function:
let response = await axios.get('/api/todo', addAuthHeader())
Refresh the page (or start the server) and the now-authenticated request will succeed once again.
Add the Okta .NET SDK
You’re almost done! The final task is to store and retrieve the user’s to-do items in the Okta custom profile attribute you set up earlier. You’ll use the Okta .NET SDK to do this in a few lines of backend code.
Stop the ASP.NET Core server (if it’s running), and install the Okta .NET SDK in your project with the
dotnet tool: = "{{yourOktaOrgUrl}}",ktaOrgUrl}}! Because of this, before? It’s time to replace it with a new service that uses Okta to store the user’s to-do items. Create
OktaTodoItemService.cs in the Services foldre: are limited to storing primitives likes strings and numbers, but you’re using the
TodoModel type to represent to-do items. This service serializes the strongly-typed }, addAuthHeader()) await dispatch('getAllTodos') }, async toggleTodo({ dispatch }, data) { await axios.post( '/api/todo/' + data.id, { completed: data.completed }, addAuthHeader()) await dispatch('getAllTodos') }, async deleteTodo({ dispatch }, id) { await axios.delete('/api/todo/' + id, addAuthHeader()) await dispatch('getAllTodos') }
Start the server one more time with
dotnet run and try adding a real to-do item to the list:
Learn More! our other recent posts:
- The Lazy Developer's Guide to Authentication with Vue.js
- Build a Cryptocurrency Comparison Site with Vue.js
Happy coding!
This content is sponsored via Syndicate Ads