├── project ├── build.properties └── plugins.sbt ├── public ├── images │ └── favicon.png ├── index.html └── javascripts │ └── jquery-1.9.0.min.js ├── .gitignore ├── app ├── Global.java ├── utils │ └── DemoData.java ├── models │ ├── Todo.java │ └── User.java ├── controllers │ ├── Secured.java │ ├── TodoController.java │ └── SecurityController.java └── assets │ └── javascripts │ └── index.coffee ├── conf ├── routes ├── evolutions │ └── default │ │ └── 1.sql └── application.conf ├── test ├── SecurityControllerTest.java ├── TodoControllerTest.java └── UserTest.java └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.5 2 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poornerd/play-rest-security/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Comment to get more information during initialization 2 | logLevel := Level.Warn 3 | 4 | // The Typesafe repository 5 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 6 | 7 | // Use the Play sbt plugin for Play projects 8 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.0") 9 | 10 | addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") 11 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Play REST Security Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/Global.java: -------------------------------------------------------------------------------- 1 | import models.User; 2 | import play.Application; 3 | import play.GlobalSettings; 4 | import play.Play; 5 | import utils.DemoData; 6 | 7 | public class Global extends GlobalSettings { 8 | 9 | @Override 10 | public void onStart(Application application) { 11 | // load the demo data in dev mode 12 | if (Play.isDev() && (User.find.all().size() == 0)) { 13 | DemoData.loadDemoData(); 14 | } 15 | 16 | super.onStart(application); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/utils/DemoData.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import models.*; 4 | import play.Logger; 5 | 6 | import java.util.ArrayList; 7 | 8 | public class DemoData { 9 | 10 | public static User user1; 11 | public static User user2; 12 | 13 | public static Todo todo1_1; 14 | public static Todo todo1_2; 15 | 16 | public static Todo todo2_1; 17 | 18 | public static void loadDemoData() { 19 | 20 | Logger.info("Loading Demo Data"); 21 | 22 | user1 = new User("user1@demo.com", "password", "John Doe"); 23 | user1.save(); 24 | 25 | todo1_1 = new Todo(user1, "make it secure"); 26 | todo1_1.save(); 27 | 28 | todo1_2 = new Todo(user1, "make it neat"); 29 | todo1_2.save(); 30 | 31 | user2 = new User("user2@demo.com", "password", "Jane Doe"); 32 | user2.save(); 33 | 34 | todo2_1 = new Todo(user2, "make it pretty"); 35 | todo2_1.save(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/models/Todo.java: -------------------------------------------------------------------------------- 1 | package models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import play.data.validation.Constraints; 5 | import play.db.ebean.Model; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import javax.persistence.ManyToOne; 11 | import java.util.List; 12 | 13 | @Entity 14 | public class Todo extends Model { 15 | 16 | @Id 17 | public Long id; 18 | 19 | @Column(length = 1024, nullable = false) 20 | @Constraints.MaxLength(1024) 21 | @Constraints.Required 22 | public String value; 23 | 24 | @ManyToOne 25 | @JsonIgnore 26 | public User user; 27 | 28 | public Todo(User user, String value) { 29 | this.user = user; 30 | this.value = value; 31 | } 32 | 33 | public static List findByUser(User user) { 34 | Finder finder = new Finder(Long.class, Todo.class); 35 | return finder.where().eq("user", user).findList(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Assets.at(path="/public", file="index.html") 7 | 8 | GET /api-docs controllers.ApiHelpController.getResources 9 | 10 | #GET /api-docs/login controllers.ApiHelpController.getResource(path = "/login") 11 | POST /login controllers.SecurityController.login() 12 | #GET /api-docs/logout controllers.ApiHelpController.getResource(path = "/logout") 13 | POST /logout controllers.SecurityController.logout() 14 | 15 | GET /api-docs/api/todos controllers.ApiHelpController.getResource(path = "/api/todos") 16 | GET /todos controllers.TodoController.getAllTodos() 17 | POST /todos controllers.TodoController.createTodo() 18 | 19 | # Map static resources from the /public folder to the /assets URL path 20 | GET /assets/*file controllers.Assets.at(path="/public", file) 21 | -------------------------------------------------------------------------------- /app/controllers/Secured.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import controllers.SecurityController; 4 | import controllers.routes; 5 | import models.User; 6 | import play.mvc.Http.Context; 7 | import play.mvc.Result; 8 | import play.mvc.Security; 9 | 10 | /** 11 | * Created by saeed on 30/June/14 AD. 12 | */ 13 | public class Secured extends Security.Authenticator { 14 | 15 | @Override 16 | public String getUsername(Context ctx) { 17 | User user = null; 18 | String[] authTokenHeaderValues = ctx.request().headers().get(SecurityController.AUTH_TOKEN_HEADER); 19 | if ((authTokenHeaderValues != null) && (authTokenHeaderValues.length == 1) && (authTokenHeaderValues[0] != null)) { 20 | user = models.User.findByAuthToken(authTokenHeaderValues[0]); 21 | if (user != null) { 22 | ctx.args.put("user", user); 23 | return user.getEmailAddress(); 24 | } 25 | } 26 | 27 | return null; 28 | } 29 | 30 | @Override 31 | public Result onUnauthorized(Context ctx) { 32 | return redirect(routes.SecurityController.login()); 33 | } 34 | } -------------------------------------------------------------------------------- /conf/evolutions/default/1.sql: -------------------------------------------------------------------------------- 1 | # --- Created by Ebean DDL 2 | # To stop Ebean DDL generation, remove this comment and start using Evolutions 3 | 4 | # --- !Ups 5 | 6 | create table todo ( 7 | id bigint not null, 8 | value varchar(1024) not null, 9 | user_id bigint, 10 | constraint pk_todo primary key (id)) 11 | ; 12 | 13 | create table user ( 14 | id bigint not null, 15 | auth_token varchar(255), 16 | email_address varchar(256) not null, 17 | sha_password varbinary(64) not null, 18 | full_name varchar(256) not null, 19 | creation_date timestamp not null, 20 | constraint uq_user_email_address unique (email_address), 21 | constraint pk_user primary key (id)) 22 | ; 23 | 24 | create sequence todo_seq; 25 | 26 | create sequence user_seq; 27 | 28 | alter table todo add constraint fk_todo_user_1 foreign key (user_id) references user (id) on delete restrict on update restrict; 29 | create index ix_todo_user_1 on todo (user_id); 30 | 31 | 32 | 33 | # --- !Downs 34 | 35 | SET REFERENTIAL_INTEGRITY FALSE; 36 | 37 | drop table if exists todo; 38 | 39 | drop table if exists user; 40 | 41 | SET REFERENTIAL_INTEGRITY TRUE; 42 | 43 | drop sequence if exists todo_seq; 44 | 45 | drop sequence if exists user_seq; 46 | 47 | -------------------------------------------------------------------------------- /app/controllers/TodoController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import com.wordnik.swagger.annotations.*; 4 | import models.Todo; 5 | import play.Logger; 6 | import play.data.Form; 7 | import play.mvc.*; 8 | 9 | import java.util.List; 10 | 11 | import static play.libs.Json.toJson; 12 | 13 | @Api(value = "/api/todos", description = "Operations with Todos") 14 | @Security.Authenticated(Secured.class) 15 | public class TodoController extends Controller { 16 | 17 | @ApiOperation(value = "get All Todos", 18 | notes = "Returns List of all Todos", 19 | response = Todo.class, 20 | httpMethod = "GET") 21 | public static Result getAllTodos() { 22 | return ok(toJson(models.Todo.findByUser(SecurityController.getUser()))); 23 | } 24 | 25 | @ApiOperation( 26 | nickname = "createTodo", 27 | value = "Create Todo", 28 | notes = "Create Todo record", 29 | httpMethod = "POST", 30 | response = Todo.class 31 | ) 32 | @ApiImplicitParams( 33 | { 34 | @ApiImplicitParam( 35 | name = "body", 36 | dataType = "Todo", 37 | required = true, 38 | paramType = "body", 39 | value = "Todo" 40 | ) 41 | } 42 | ) 43 | @ApiResponses( 44 | value = { 45 | @com.wordnik.swagger.annotations.ApiResponse(code = 400, message = "Json Processing Exception") 46 | } 47 | ) 48 | public static Result createTodo() { 49 | Form form = Form.form(models.Todo.class).bindFromRequest(); 50 | if (form.hasErrors()) { 51 | return badRequest(form.errorsAsJson()); 52 | } 53 | else { 54 | models.Todo todo = form.get(); 55 | todo.user = SecurityController.getUser(); 56 | todo.save(); 57 | return ok(toJson(todo)); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/controllers/SecurityController.java: -------------------------------------------------------------------------------- 1 | package controllers; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | import models.User; 5 | import play.Logger; 6 | import play.data.Form; 7 | import play.data.validation.Constraints; 8 | import play.libs.F; 9 | import play.libs.Json; 10 | import play.mvc.*; 11 | 12 | import static play.libs.Json.toJson; 13 | import static play.mvc.Controller.request; 14 | import static play.mvc.Controller.response; 15 | 16 | public class SecurityController extends Controller { 17 | 18 | public final static String AUTH_TOKEN_HEADER = "X-AUTH-TOKEN"; 19 | public static final String AUTH_TOKEN = "authToken"; 20 | 21 | 22 | public static User getUser() { 23 | return (User)Http.Context.current().args.get("user"); 24 | } 25 | 26 | // returns an authToken 27 | public static Result login() { 28 | Form loginForm = Form.form(Login.class).bindFromRequest(); 29 | 30 | if (loginForm.hasErrors()) { 31 | return badRequest(loginForm.errorsAsJson()); 32 | } 33 | 34 | Login login = loginForm.get(); 35 | 36 | User user = User.findByEmailAddressAndPassword(login.emailAddress, login.password); 37 | 38 | if (user == null) { 39 | return unauthorized(); 40 | } 41 | else { 42 | String authToken = user.createToken(); 43 | ObjectNode authTokenJson = Json.newObject(); 44 | authTokenJson.put(AUTH_TOKEN, authToken); 45 | response().setCookie(AUTH_TOKEN, authToken); 46 | return ok(authTokenJson); 47 | } 48 | } 49 | 50 | @Security.Authenticated(Secured.class) 51 | public static Result logout() { 52 | response().discardCookie(AUTH_TOKEN); 53 | getUser().deleteAuthToken(); 54 | return redirect("/"); 55 | } 56 | 57 | public static class Login { 58 | 59 | @Constraints.Required 60 | @Constraints.Email 61 | public String emailAddress; 62 | 63 | @Constraints.Required 64 | public String password; 65 | 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="=OkDiO<3DH;W0_QRTZV44YiZW[[fx5>x>PH@A>MMBI^S[2pP4Xvv5aIs^;X1] init() 2 | 3 | 4 | init = () -> 5 | # try to get an auth token 6 | document.cookie.split('; ').forEach (cookieString) -> 7 | cookie = cookieString.split("=") 8 | if ((cookie.length == 2) && (cookie[0] == "authToken")) 9 | window.authToken = cookie[1] 10 | 11 | if (window.authToken == undefined) 12 | displayLoginForm() 13 | else 14 | displayTodos() 15 | 16 | 17 | displayLoginForm = () -> 18 | $("body").empty() 19 | loginForm = $("
").attr("action", "/login").attr("method", "post").attr("id", "loginForm") 20 | loginForm.append $("").attr("id", "emailAddress").attr("name", "emailAddress").val("user1@demo.com") 21 | loginForm.append $("").attr("id", "password").attr("name", "password").attr("type", "password").val("password") 22 | loginForm.append $("").attr("type", "submit").val("Login") 23 | loginForm.submit (event) -> 24 | event.preventDefault() 25 | $.ajax 26 | url: event.currentTarget.action 27 | type: event.currentTarget.method 28 | dataType: 'json' 29 | contentType: 'application/json' 30 | data: JSON.stringify({emailAddress: $("#emailAddress").val(), password: $("#password").val()}) 31 | error: (jqXHR, errorText, error) -> 32 | displayError("Login failed") 33 | success: doLogin 34 | $("body").append loginForm 35 | 36 | 37 | doLogin = (data, textStatus, jqXHR) -> 38 | window.authToken = data.authToken # global state holder for the auth token 39 | $("#loginForm").remove() 40 | displayTodos() 41 | 42 | 43 | displayTodos = () -> 44 | fetchTodos() 45 | $("body").empty() 46 | $("body").append $("