Add Auth0 JWT support

This commit is contained in:
Vikram Rangnekar 2019-03-28 22:34:42 -04:00
parent a897158bcc
commit 83f90c1bbd
8 changed files with 81 additions and 12 deletions

View File

@ -2,3 +2,4 @@ example
tmp
*.md
web/build
docker-compose.*

View File

@ -234,7 +234,7 @@ auth:
# type: jwt
# cookie: _app_session
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:
@ -283,10 +283,44 @@ SG_AUTH_URL
SG_AUTH_PASSWORD
```
## Authentication
You can only have one type of authentication enabled. You can either pick Rails or JWT. Uncomment the one you use and leave the rest commented out.
#### JWT Tokens
```yaml
auth:
type: jwt
provider: auth0 #none
cookie: _app_session
secret: abc335bfcfdb04e50db5bb0a4d67ab9
public_key_file: /secrets/public_key.pem
public_key_type: ecdsa #rsa
```
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 verified either a `secret` or a public key (ecdsa or rsa) is required. When using public keys they have to be in a PEM format file.
## Deployment
How do I deploy the Super Graph service with my existing rails app? You have several options here. Esentially you need to ensure your app's session cookie
will be passed to this service.
How do I deploy the Super Graph service with my existing rails app? You have several options here. Esentially you need to ensure your app's session cookie will be passed to this service.
#### Custom Docker Image
Create a `Dockerfile` like the one below to roll your own
custom Super Graph docker image. And to build it `docker build -t my-super-graph .`
```dockerfile
FROM dosco/super-graph:latest
WORKDIR /app
COPY *.yml ./
```
#### Deploy under a subdomain
For this to work you have to ensure that the option `:domain => :all` is added to your rails app config `Application.config.session_store` this will cause your rails app to create session cookies that can be shared with sub-domains. More info here <http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/>

View File

@ -50,9 +50,10 @@ auth:
# auth:
# type: jwt
# provider: auth0
# cookie: _app_session
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/dosco/super-graph
require (
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/garyburd/redigo v1.6.0

View File

@ -43,9 +43,10 @@ auth:
# auth:
# type: jwt
# provider: auth0
# cookie: _app_session
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:

View File

@ -15,8 +15,9 @@ const (
)
var (
userIDKey = struct{}{}
errSessionData = errors.New("error decoding session data")
userIDProviderKey = struct{}{}
userIDKey = struct{}{}
errSessionData = errors.New("error decoding session data")
)
func headerHandler(next http.HandlerFunc) http.HandlerFunc {

View File

@ -4,15 +4,27 @@ import (
"context"
"io/ioutil"
"net/http"
"strings"
jwt "github.com/dgrijalva/jwt-go"
)
const (
jwtBase int = iota
jwtAuth0
)
func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
var key interface{}
var jwtProvider int
cookie := conf.GetString("auth.cookie")
provider := conf.GetString("auth.provider")
if provider == "auth0" {
jwtProvider = jwtAuth0
}
conf.BindEnv("auth.secret", "SG_AUTH_SECRET")
secret := conf.GetString("auth.secret")
@ -75,7 +87,17 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
}
if claims, ok := token.Claims.(*jwt.StandardClaims); ok {
ctx := context.WithValue(r.Context(), userIDKey, claims.Id)
ctx := r.Context()
if jwtProvider == jwtAuth0 {
sub := strings.Split(claims.Subject, "|")
if len(sub) != 2 {
ctx = context.WithValue(ctx, userIDProviderKey, sub[0])
ctx = context.WithValue(ctx, userIDKey, sub[1])
}
} else {
ctx = context.WithValue(ctx, userIDKey, claims.Subject)
}
next.ServeHTTP(w, r.WithContext(ctx))
}

View File

@ -153,15 +153,24 @@ func authCheck(ctx context.Context) bool {
}
func varValues(ctx context.Context) map[string]interface{} {
userIDFn := fasttemplate.TagFunc(func(w io.Writer, _ string) (int, error) {
uidFn := fasttemplate.TagFunc(func(w io.Writer, _ string) (int, error) {
if v := ctx.Value(userIDKey); v != nil {
return w.Write([]byte(v.(string)))
}
return 0, errNoUserID
})
uidpFn := fasttemplate.TagFunc(func(w io.Writer, _ string) (int, error) {
if v := ctx.Value(userIDProviderKey); v != nil {
return w.Write([]byte(v.(string)))
}
return 0, errNoUserID
})
return map[string]interface{}{
"USER_ID": userIDFn,
"user_id": userIDFn,
"USER_ID": uidFn,
"user_id": uidFn,
"USER_ID_PROVIDER": uidpFn,
"user_id_provider": uidpFn,
}
}