This is an Elide-based post and commenting system with real user authentication. While authentication is simple, this is intended to be an illustrative example of using Elide. It uses Elide standalone to create the service.
Building this application only requires one step. It will build a completely self-contained application jar.
$ ./gradlew build
Before running the application, please be sure your MySQL database is setup properly. The application's default settings assume you have a MySQL server running with the following settings:
Setting | Value |
---|---|
Host | localhost |
Port | 3306 |
Database | elide |
User | elide |
Password | elide123 |
If you wish to change these settings, then please modify the hibernate.cfg.xml.
After your MySQL database is up and running, run:
$ java -jar build/libs/sample-app-1.0-SNAPSHOT-all.jar
Your application is now started and your web service should now be running on port 5050
.
This sample application applies principles useful in building real world apps. Namely, it provides a mechanism for performing real database-backed security as well as usage of finely tuned security rules.
The code is outlined as follows:
src/main/kotlin/com/dennismcwherter/elide/app
├── filters
│ └── UserAuthFilter.kt
├── models
│ ├── Account.kt
│ ├── Comment.kt
│ ├── Post.kt
│ └── package-info.java
└── security
├── Settings.kt
├── checks
│ ├── AccountChecks.kt
│ ├── OwnedEntityChecks.kt
│ └── UserChecks.kt
├── context
│ ├── AccountPrincipal.kt
│ └── ApplicationSecurityContext.kt
└── interfaces
└── OwnedEntity.kt
A brief explanation of the directories is below:
Directory | Description |
---|---|
filters | Contains JAX-RS/Jersey web filters |
models | Contains JPA annotated API models |
security | Contains all security related code |
security/checks | Contains all security check implementations |
security/context | Holds JAX-RS security context information |
security/interfaces | Holds common security check interfaces |
The API is generated by http://elide.io using the models found in the models package. For more information about how Elide works, please visit Elide's homepage.
User authentication is performed in the UserAuthFilter
. While Elide can properly authorize
users, it does not handle authentication
out of box. As a result, we use a simple web filter to parse the User
and Password
headers and determine whether or not proper credentials were provided.
While there are more complex schemes to handle authentication (i.e. microservices via oauth, tokens, etc.) this is a real mechanism that could be used. However, it is likely one would want to make some slight modifications to pass secure, revokable credentials (i.e. tokens) rather than constantly sending the username and password with each request.
Security rule implementations are broken out into the security/checks
directory. The idea is that these are single units of computation that can then be combined in our expressions to maximize check re-usability.
We have examples of these checks operating on data as well as user principal objects.
The GraphQL-based API has a single endpoint:
/graphql/api/v1
You can use one of 2 top-level (i.e. rootable) objects to manipulate this API:
Object | Description |
---|---|
account | Used for all account actions. Can return current user account info. |
post | Used for all post actions. Contains all posts. |
Additionally, over HTTP we use the json
format to send our request. Similarly, we support additional headers for login. See below for an outlined list:
Header | Value |
---|---|
Content-Type | application/json |
Accept | application/json |
User* | <your username> |
Password* | <your password> |
* Only required if performing an action as logged in user
The JSON-API-based API supports 2 top-level (i.e. rootable) endpoints:
Endpoint | Description |
---|---|
/account | Used for all account actions. Can return current user account info. |
/post | Used for all post actions. Contains all posts. |
The /account
endpoint can be hit by any user (though only if you are logged in will you see your information); it's also how accounts are created.
The /post
endpoint can be read by any user, but can only be updated by logged in users.
If you've looked through the code you will notice that we also have a Comment
entity. This entity is not rootable and, therefore, cannot be accessed via /comments
. However, with similar access permissions as Post
, users can access a post's comments by visiting /post/ID/comments
.
Since this is a JSONAPI endpoint, you will have to supply the proper content headers. Similarly, we support login through headers as well:
Header | Value |
---|---|
Content-Type | application/vnd.api+json |
Accept | application/vnd.api+json |
User* | <your username> |
Password* | <your password> |
* Only required if performing an action as logged in user
$ curl -X POST \
http://127.0.0.1:5050/graphql/api/v1 \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-d '{
"query": "mutation { account(op:UPSERT, data:{name:\"user1\", newPassword:\"123\"}) { edges { node { id name } } } }"
}
'
$ curl -X POST \
http://127.0.0.1:5050/api/v1/account \
-H 'accept: application/vnd.api+json' \
-H 'content-type: application/vnd.api+json' \
-d '{
"data": {
"type": "account",
"id": 1,
"attributes": {
"name": "user1",
"newPassword": "123"
}
}
}'
curl -X POST \
http://127.0.0.1:5050/graphql/api/v1 \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'password: 123' \
-H 'user: user1' \
-d '{
"query": "mutation { post(op:UPSERT, data:{content:\"This is my very first post!!!\", owner:{id:\"1\"}}) { edges { node { id content owner { edges { node { id } } } } } } }"
}
'
$ curl -X POST \
http://127.0.0.1:5050/api/v1/post \
-H 'accept: application/vnd.api+json' \
-H 'content-type: application/vnd.api+json' \
-H 'password: 123' \
-H 'user: user1' \
-d '{
"data": {
"type": "post",
"id": 1,
"attributes": {
"content": "This is my very first post!!!"
},
"relationships": {
"owner": {
"data": { "type": "account", "id": "1" }
}
}
}
}'
curl -X POST \
http://127.0.0.1:5050/graphql/api/v1 \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'password: 123' \
-H 'user: user1' \
-d '{
"query": "mutation { post(ids: \"1\") { edges { node { comments(op:UPSERT, data:{content:\"This is my first comment. It is very nice!\", owner:{id:\"1\"}}) { edges { node { id content owner { edges { node { id } } } } } } } } } }"
}
'
$ curl -X POST \
http://127.0.0.1:5050/api/v1/post/1/comments \
-H 'accept: application/vnd.api+json' \
-H 'content-type: application/vnd.api+json' \
-H 'password: 123' \
-H 'user: user1' \
-d '{
"data": {
"type": "comment",
"id": 1,
"attributes": {
"content": "This is my first comment. It is very nice!"
},
"relationships": {
"owner": {
"data": { "type": "account", "id": "1" }
}
}
}
}'
$ curl -X POST \
http://127.0.0.1:5050/graphql/api/v1 \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-d '{
"query" : "{ post { edges { node { id content comments { edges { node { id content } } } } } } }"
}'
$ curl -X GET http://127.0.0.1:5050/api/v1/post?include=comments | python -m json.tool