Service
GraphJin Standalone is a docker packaged GO build that you can run as a service on it's own. It exposes REST, GraphQL and Websocket APIs and can handle various authentications like JWT, Rails cookies, etc.
It is very fast, secure and has a ton of API best practices built in like Rate Limiting
, ETags & Cache Headers
, Compression
, etc
TOC
- Trying out the example
- Install GraphJin
- Add a database schema
- Defines tables and database relationships
- Create a seed script
- Setup the database
- Start the Webshop API
- GraphJin WebUI
- Fetch data with GraphQL
- Secrets management
- Authentication
Trying out the example
link
For this example we will create a example e-commerce store. This example app can be found in repo.
Below we explain how this example app was built and other details around useing GraphJin to make you more productive.
git clone https://github.com/dosco/graphjin.git
cd graphjin/examples/webshop
docker compose run api db setup
docker compose up
Install GraphJin
link
1. Quick Install
# Mac (Homebrew)
brew install dosco/graphjin/graphjin
# Ubuntu (Snap)
sudo snap install --classic graphjin
Debian and Redhat (releases) download the .deb or .rpm from the releases page and install with dpkg -i and rpm -i respectively.
2. Create a new API
Let's call our app Webshop.
graphjin new webshop
cd webshop
Add a database schema
link
graphjin db migrate new users
graphjin db migrate new products
graphjin db migrate new sections
graphjin db migrate new customers
graphjin db migrate new purchases
graphjin db migrate new notifications
# delete the example migration
rm -rf config/migrations/0_init.sql
Defines tables and database relationships
link
Be sure to define primary keys to all of your tables and to use foreign keys to define
relationships between tables. In the example below the products table has a foreign key relationhsip user_id
to the users table. It looks like this user_id bigint REFERENCES users(id)
-- Write your migrate up statements here
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
descriptioj TEXT,
user_id BIGINT REFERENCES users(id)
);
Create a seed script
link
This step is optional. A seed script inserts fake data into the database. It helps frontend developers if they already have some fake data to work with. The seed script is written in javascript and data is inserted into the database using GraphQL.
// Ceate 100 fake users
for (i = 0; i < 100; i++) {
// Fake data functions are built into GraphJin
var data = {
full_name: fake.name(),
avatar: fake.avatar_url(),
phone: fake.phone(),
email: "user" + i + "@demo.com",
};
// Graphql mutation query to insert user
var res = graphql(
"mutation { users(insert: $data) { id } }",
{ data: data },
{ user_id: -1 }
);
}
Setup the database
link
Ensure you have a Postgres database running and the config file has the correct connection details to it.
graphjin db setup
Start the Webshop API
link
graphjin serve
GraphJin WebUI
link
The GraphJin web UI is used to build and test queries. It supports auto-completion which makes it easy to craft queries. Open your web browser and visit the below url.
Fetch data with GraphQL
link
query getProducts {
products {
id
name
description
customers {
id
email
}
}
}
{
"data": {
"products": [
{
"id": 1,
"name": "Oak Aged Yeti Imperial Stout",
"customers": [
{
"id": 2,
"email": "johannahagenes@considine.com"
},
{
"id": 2,
"email": "johannahagenes@considine.com"
}
],
"description": "Belgian And French Ale, Galena, Carapils"
},
...
]
}
}
Secrets management
link
We recommend you use Mozilla SOPS for secrets management. The sops binary is installed on the GraphJin app docker image. To use SOPS you create a yaml file with your secrets like the one below. You then need a secret key to encrypt it. Your options are to go with Google Cloud KMS, Amazon KMS, Azure Key Vault, etc. In production SOPS will automatically fetch the key from your defined KMS, decrypt the secrets file and make the values available to GraphJin via enviroment variables.
- Create the secrets file
SG_DATABASE_PASSWORD: postgres
SG_AUTH_JWT_SECRET: jwt_token_secret_key
SG_SECRET_KEY: generic_secret_ke
- Login to your cloud (Google Example)
gcloud auth login
gcloud auth application-default login
- Encrypt the secrets with the key
sops -e -i ./config/prod.secrets.yml
Authentication
link
You can only have one type of auth enabled either Rails or JWT.
Ruby on Rails
Almost all Rails apps use Devise or Warden for authentication. Once the user is
authenticated a session is created with the users ID. The session can either be
stored in the users browser as a cookie, memcache or redis. If memcache or redis is used then a cookie is set in the users browser with just the session id.
GraphJin can handle all these variations including the old and new session formats. Just enable the right auth
config based on how your rails app is configured.
Cookie session store
auth:
type: rails
cookie: _app_session
rails:
# Rails version this is used for reading the
# various cookies formats.
version: 5.2
# Found in 'Rails.application.config.secret_key_base'
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
Memcache session store
auth:
type: rails
cookie: _app_session
rails:
# Memcache remote cookie store.
url: memcache://127.0.0.1
Redis session store
auth:
type: rails
cookie: _app_session
rails:
# Redis remote cookie store
url: redis://127.0.0.1:6379
password: ""
max_idle: 80
max_active: 12000
JWT Tokens
auth:
type: jwt
jwt:
# valid providers are auth0, firebase, jwks and none
provider: auth0
secret: abc335bfcfdb04e50db5bb0a4d67ab9
public_key_file: /secrets/public_key.pem
public_key_type: ecdsa #rsa
issuer: https://my-domain.auth0.com
audience: my_client_id
For JWT tokens we currently support tokens from a provider like Auth0 or if you have a custom solution then we look for the user_id
in the subject
claim of of the id token
. If you pick Auth0 then we derive two variables from the token user_id
and user_id_provider
for to use in your filters.
We can get the JWT token either from the authorization
header where we expect it to be a bearer
token or if cookie
is specified then we look there.
For validation a secret
or a public key (ecdsa or rsa) is required. When using public keys they have to be in a PEM format file.
Setting issuer
is recommended but not required. When specified it's going to be compared against the iss
claim of the JWT token.
Also audience
is recommended but not required. When specified it's going to be compared against the aud
claim of the JWT token. The aud
claim usually identifies the intended recipient of the token. For Auth0 is the client_id, for other provider could be the domain URL.
Firebase Auth
auth:
type: jwt
jwt:
provider: firebase
audience: <firebase-project-id>
Firebase auth also uses JWT the keys are auto-fetched from Google and used according to their documentation mechanism. The audience
config value needs to be set to your project id and everything else is taken care for you.
Setting issuer
is not required for Firebase, it's going to be automatically defined using the audience
as "https://securetoken.google.com/
JWKS Auth
auth:
type: jwt
jwt:
provider: jwks
issuer: https://accounts.google.com
audience: 1234987819200.apps.googleusercontent.com
jwks_url: https://www.googleapis.com/oauth2/v3/certs
jwks_min_refresh: 30
The JWKS provider downloads and keeps track of keys which are automatically refreshed from a JWKS endpoint, like "https://YOUR_DOMAIN/.well-known/jwks.json".
Interval between refreshes could be calculated in two ways:
- You can set an explicit refresh interval in minutes by using
jwks_refresh
. In this mode, it doesn't matter what the HTTP response says in its Cache-Control or Expires headers. - If
jwks_refresh
is not defined, then the time to refresh is automatically calculated based on the key's Cache-Control or Expires headers. You could define an absolute minimum interval before refreshes in minutes withjwks_min_refresh
. This value is used as a fallback value when tokens are refreshed, if unspecified, the minimum refresh interval is 60 minutes.
We can get the JWT token either from the authorization
header where we expect it to be a bearer
token or if cookie
is specified then we look there.
Setting issuer
is recommended but not required. When specified it's going to be compared against the iss
claim of the JWT token.
Also audience
is recommended but not required. When specified it's going to be compared against the aud
claim of the JWT token. The aud
claim usually identifies the intended recipient of the token. For Auth0 is the client_id, for other provider could be the domain URL.
HTTP Headers
header:
name: X-AppEngine-QueueName
exists: true
#value: default
Header auth is usually the best option to authenticate requests to the action endpoints. For example you
might want to use an action to refresh a materalized view every hour and only want a cron service like the Google AppEngine Cron service to make that request in this case a config similar to the one above will do.
The exists: true
parameter ensures that only the existance of the header is checked not its value. The value
parameter lets you confirm that the value matches the one assgined to the parameter. This helps in the case you are using a shared secret to protect the endpoint.