GithubHelp home page GithubHelp logo

mean-auth-blog's Introduction

MEAN Auth Blog

You have been provided with MEAN Stack Application with user authentication fully baked in. Visitors to your site can sign up, login, and logout. Now it's time to add authorization so that only certain visitors (i.e. logged in users) can create, update, and destroy blog posts.

Authentication vs. Authorization

Authentication is like the bouncer at a bar checking your drivers license. Does your face match the one in the picture (i.e. your username)? Do you know your birthdate? (i.e. your password)? Good. You're in.

Authorization is like the "Class c" on your license which gives you permission to drive cars. Only some motorists are allowed to ride 'motorcycles' and drive 'semi-trucks'.

App Setup

  1. Clone this repo.

  2. From inside the project directoy, run npm install to install the required node modules.

  3. In one Terminal window, run mongod, and in another, run nodemon.

  4. Run the seed tasks (node seed.js) to create a default user and some posts.

  5. Navigate to localhost:9000 in the browser. You should see an empty page and an angry red error message in the Chrome console.

You should see Error: ENOENT, no such file or directory '.env' in your terminal

  1. To fix it, add a "dot env" file, called .env, to the root directory. Add this line to the file:
TOKEN_SECRET=yoursupersecrettoken

This is the secret your server will use to encode the JWT token for each user. Note that this file is listed in .gitignore because you never want to expose your secret tokens on github.

  1. Before hooking up the front-end, test your server routes via Postman:
  • Send a GET request to /api/me. You should see the message: "Please make sure your request has an Authorization header."
  • Send a POST request to /auth/signup with a test email and password. You should see a token that was generated by the server.

    Make sure to use x-www-form-urlencoded and send your data in the body of the request).

  • Send a POST request to /auth/login with the email and password you just used to create a new user. You should again see a token that was generated by the server.
  • Bonus: If you add your JWT token in the Authorization header like so: "Bearer YOUR-UNIQUE-TOKEN" you will be permitted/authorized to create/update/destroy blog posts.

Challenge 1: CRUDing Blog Posts

You have been supplied with a posts#index controller that displays the current blog posts.

Your goal is to: Build out angular templates, controllers, and routes for new, edit, and show, with easy navigation between these pages.

1.1 PostsNewController (posts-new.controller.js)

To begin, let's create a new angular controller, template, and route for creating a single blog post.

  • When a user visits /posts/new on the client...
    • they should see an empty form
      • with a field for title (use ng-model="postsNewCtrl.post.title")
      • and a field for content (use ng-model="postsNewCtrl.post.content")
      • and a button to "save".
  • When a user clicks "save" (having filled out the form)...
    • they should trigger a on-submit="postsNewCtrl.create()" method on the form(!)
    • and the create() method should make an $http request to POST /api/posts using the data in vm.post.
    • and their post should be saved to the database
    • and the server should respond with the newly created post and _id (e.g. 12345)
    • and the user should be redirect to e.g. /posts/12345.
Javascript Solution (Click Here) ```js PostsNewController.$inject = ["$location", "$http"]; // minification protection function PostsNewController ($location, $http) { var vm = this; vm.create = create; vm.post = {}; // form data

////

function create() { $http .post('/api/posts', vm.post) .then(onCreateSuccess, onCreateError);

function onCreateSuccess(response){
  $location.path('/posts/' + response.data._id)
}

function onCreateError(response){
  console.error("Failed to create post", response);
}

}; }

</details>

<details>
<summary>HTML Solution (Click Here)</summary>
```html
<form ng-submit="postsNewCtrl.create()">
  <div class="form-group">
    <input type="text" class="form-control" placeholder="Title" ng-model="postsNewCtrl.post.title">
  </div>
  <div class="form-group">
    <textarea class="form-control" placeholder="Content" ng-model="postsNewCtrl.post.content"></textarea>
  </div>
  <input type="submit" value="Create Post" class="btn btn-block btn-info">
</form>

1.2 PostsShowController (posts-show.controller.js)

  • When a user visits /posts/12345 on the client...
    • they should make an $http request to GET /api/posts/12345...
    • and they should see the title and content of post 12345...
      • using e.g. {{postsShowCtrl.title}}
    • and a button to edit
      • and be redirected to /posts/12345/edit
    • and a button to delete
      • and should trigger a on-click="postsShowCtrl.destroy()" method on the button(!)
      • and the destroy() methods should make an $http request to DELETE /api/posts/12345
      • and on success, be redirected to / or /posts (posts index).
      • BONUS: and see the message "Successfully deleted post" below the navbar.
Javascript Solution (Click Here) ```js PostsShowController.$inject = ["$location", "$http", "$routeParams"]; // minification protection function PostsShowController ($location, $http, $routeParams) { var vm = this; vm.post = {};

var id = $routeParams.id; get(); // fetch one post (show)

////

function get() { $http .get('/api/posts/' + id) .then(onGetSuccess, onGetError);

function onGetSuccess(response){
  vm.post = response.data;
}

function onGetError(response){
  console.error("Failed to get post", response);
  $location.path("/");
}

}; }

</details>

<details>
<summary>HTML Solution (Click Here)</summary>
```html
<h2>{{postsShowCtrl.post.title}}</h2>
<p>{{postsShowCtrl.post.content}}</p>
<a class="btn btn-primary" ng-href="/posts/{{postsShowCtrl.post._id}}/edit">Edit Post</a>

1.3 PostsEditController (posts-edit.controller.js)

  • When a user visits /posts/12345/edit on the client...
    • they should make an $http request to GET /api/posts/12345...
    • and see a pre-populated form for post 12345...
      • with a field for title (use ng-model="postsEditCtrl.post.title")
      • and a field for content(use ng-model="postsEditCtrl.post.content")
    • and a button to Discard Changes
      • and be redirected to /posts/12345
      • BONUS: and a pop-up, confirmation dialog that says "Are you sure you want to discard your changes?"
    • and a button to Save Changes
      • that should trigger a on-submit="postsEditCtrl.update()" on the form(!)
      • and the update() method should make an $http request to the server, using the data in vm.post
      • and on success, be redirected to /posts/12345 (show).
    • and a button to Delete Post
      • that should trigger a on-click="postsEditCtrl.destroy()" on the button
      • and the destroy() method should make an $http delete request to the server
      • and on success, redirect the user to / or /posts (index)
      • BONUS: and a pop-up, confirmation dialog that says "Are you sure you want to delete this post?"
Javascript Solution (Click Here) ```js PostsEditController.$inject = ["$location", "$http", "$routeParams"]; // minification protection function PostsEditController ($location, $http, $routeParams) { var vm = this; vm.update = update; vm.destroy = destroy; vm.post = {}; // form data

var id = $routeParams.id; get(); // fetch one post (show)

////

function update() { $http .put('/api/posts/' + id, vm.post) .then(onUpdateSuccess, onUpdateError);

function onUpdateSuccess(response){
  $location.path("/posts/" + id);
}

function onUpdateError(response){
  console.error("Failed to update post", response);
}

}

function destroy() { $http .delete('/api/posts/' + id) .then(onDeleteSuccess, onDeleteError);

function onDeleteSuccess(response){
  $location.path("/");
}

function onDeleteError(response){
  console.error("Failed to delete post", response);
}

}

function get() { $http .get('/api/posts/' + id) .then(onGetSuccess, onGetError);

function onGetSuccess(response){
  vm.post = response.data;
}

function onGetError(response){
  console.error("Failed to get post", response);
  $location.path("/");
}

}; }

</details>

<details>
<summary>HTML Solution (Click Here)</summary>
```html
<div class="pull-right col-xl-4">
  <a class="btn btn-warning col-xl-2" ng-href="/">Discard Changes</a>
  <a class="btn btn-danger col-xl-2" ng-click="postsEditCtrl.destroy()">Delete Post</a>
  <hr>
</div>
<form ng-submit="postsEditCtrl.update()">
  <div class="form-group">
    <input type="text" class="form-control" placeholder="Title" ng-model="postsEditCtrl.post.title">
  </div>
  <div class="form-group">
    <textarea class="form-control" placeholder="Content" ng-model="postsEditCtrl.post.content"></textarea>
  </div>
  <input type="submit" value="Update Post" class="btn btn-block btn-info">
</form>

Challenge 2: Add Authorization

We are using Satellizer (an angular library), JWT Simple (an npm library for creating and parsing JWT tokens), and our own custom authentication middleware (see middleware/auth.js).

Before adding authorization*, please familiarize yourself with middleware/auth.js. Try to answer the following questions:

  • What is a JWT token?
    • Where does it live?
  • What does it mean to be "logged in"?
    • How does the server "log in" a user?
    • How does the client know a user is "logged in"?
  • What does it mean to "log out"?
    • How does the client "log out"?

Mega Hint: Authorization is already in place for client & server routes like login. How is it done?

Objective

Your goal is to:

  • Protect sensitive endpoints on the server (like post, put, delete).
    • Only a logged in user should be able to hit API endpoints for creating, updating, and destroying blog posts (on the server).
  • Protect sensitive endpoints on the client (like new, edit, and the ability to delete).
    • Only a logged in user should be able to see the forms and buttons for creating, updating, and destroying blog posts (on the client).
    • Stretch Goal: A user should only be able to edit or delete their own blog posts (on the client). They should not see options to edit or delete on posts that do not belong to them.

2.1 Protecting Server Routes (Server)

You will want to use auth.ensureAuthenticated (see middleware/auth.js) in the route to find the current user (i.e. so that you can use req.user to access the current user).

Protected Server Routes Hint (Click Here) ```js // server.js app.post('/api/posts', auth.ensureAuthenticated, postsCtrl.create); ```

2.2 Hiding Buttons from Visitors (Client)

  • Only logged in users should be able to see buttons for new, edit, and delete.
    • ng-show="main.currentUser.isLoggedIn()"
  • BONUS: Only the owner of the blog post should see options to edit and delete the post.
    • Given that you have access to a post object, and the currentUser object, inside your controllers, is there a way to determine ownership?
Ownership Hint (Click Here) ```js someCtrl.post.user._id === main.currentUser.user_id; // watch out for undefined! ```

2.3 Protecting Client Routes (Client)

  • Only a logged in user should be able to visit pages for new and edit.
    • You will want to use loginRequired (see public/scripts/routes.js) in the route to ensure that only a logged in user can go to new and edit pages.
Protected Routes Hint (Click Here) ```js // public/scripts/routes.js

.when('/posts/new', { templateUrl: 'templates/posts/new.html', controller: 'PostsNewController', controllerAs: 'postsNewCtrl', resolve: { loginRequired: loginRequired // this is the important part } })

</details>


## Bonuses
1. Refactor to use a PostService (or a `Post` resource using `ngResource`), and inject your service into each of your post controllers.

1. Validate blog-posts! Ensure a user can't submit an empty title or content. (Use both backend AND frontend validations).

1. On the user profile page, the "Joined" date isn't formatted very nicely. Use Angular's built-in <a href="https://docs.angularjs.org/api/ng/filter/date" target="_blank">date filter</a> to display the date in this format: `January 25, 2016`.

mean-auth-blog's People

Contributors

ajbraus avatar cameronjacoby avatar nathanallen avatar adrianavillagran avatar mtvillwock avatar hitman666 avatar jlopker avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.