From 53784d9237e23fd54cac2961f126fbb03637894a Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 12 Sep 2015 18:16:04 -0700 Subject: [PATCH 01/18] Preparing directory for jwtauth module --- jwtauth/.addthis | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 jwtauth/.addthis diff --git a/jwtauth/.addthis b/jwtauth/.addthis new file mode 100644 index 0000000..e69de29 From 64fb07fc95746582bd071a8287e0b4fa732f2227 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 29 Aug 2015 17:30:30 -0700 Subject: [PATCH 02/18] Pushing JWT Auth Module for revel go framework --- jwtauth/.gitignore | 31 ++++ jwtauth/README.md | 1 + jwtauth/app/controllers/auth.go | 101 ++++++++++++ jwtauth/app/jwt/jwt.go | 276 ++++++++++++++++++++++++++++++++ jwtauth/app/models/user.go | 6 + jwtauth/conf/routes | 4 + jwtauth/jwtauth.sublime-project | 18 +++ 7 files changed, 437 insertions(+) create mode 100644 jwtauth/.gitignore create mode 100644 jwtauth/README.md create mode 100644 jwtauth/app/controllers/auth.go create mode 100644 jwtauth/app/jwt/jwt.go create mode 100644 jwtauth/app/models/user.go create mode 100644 jwtauth/conf/routes create mode 100644 jwtauth/jwtauth.sublime-project diff --git a/jwtauth/.gitignore b/jwtauth/.gitignore new file mode 100644 index 0000000..5b59b1a --- /dev/null +++ b/jwtauth/.gitignore @@ -0,0 +1,31 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# revel framework +test-results/ +tmp/ +routes/ + +*.sublime-workspace \ No newline at end of file diff --git a/jwtauth/README.md b/jwtauth/README.md new file mode 100644 index 0000000..968949f --- /dev/null +++ b/jwtauth/README.md @@ -0,0 +1 @@ +# JWT Auth module for Revel \ No newline at end of file diff --git a/jwtauth/app/controllers/auth.go b/jwtauth/app/controllers/auth.go new file mode 100644 index 0000000..ab3f287 --- /dev/null +++ b/jwtauth/app/controllers/auth.go @@ -0,0 +1,101 @@ +package controllers + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/jeevatkm/jwtauth/app/jwt" + "github.com/jeevatkm/jwtauth/app/models" + + "github.com/revel/revel" + "github.com/revel/revel/cache" +) + +type Auth struct { + *revel.Controller +} + +func (c *Auth) Token() revel.Result { + user, err := c.parseUserInfo() + if err != nil { + revel.ERROR.Printf("Unable to read user info %q", err) + c.Response.Status = http.StatusBadRequest + return c.RenderJson(map[string]string{ + "id": "bad_request", + "message": "Unable to read user info", + }) + } + + if subject, pass := jwt.Authenticate(user.Username, user.Password); pass { + token, err := jwt.GenerateToken(subject) + if err != nil { + c.Response.Status = http.StatusInternalServerError + return c.RenderJson(map[string]string{ + "id": "server_error", + "message": "Unable to generate token", + }) + } + + return c.RenderJson(map[string]string{ + "token": token, + }) + } + + c.Response.Status = http.StatusUnauthorized + c.Response.Out.Header().Set("WWW-Authenticate", jwt.Realm) + + return c.RenderJson(map[string]string{ + "id": "unauthorized", + "message": "Invalid credentials", + }) +} + +func (c *Auth) RefreshToken() revel.Result { + claims := c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{}) + revel.INFO.Printf("Claims: %q", claims) + + tokenString, err := jwt.GenerateToken(claims[jwt.SUBJECT_KEY].(string)) + if err != nil { + c.Response.Status = http.StatusInternalServerError + return c.RenderJson(map[string]string{ + "id": "server_error", + "message": "Unable to generate token", + }) + } + + // Issued new token and adding existing token into blocklist for remaining validitity period + // Let's say if existing token is valid for another 10 minutes, then it reside 10 mintues + // in the blocklist + go addToBlocklist(c.Request, claims) + + return c.RenderJson(map[string]string{ + "token": tokenString, + }) +} + +func (c *Auth) Logout() revel.Result { + // Auth token will be added to blocklist for remaining token validitity period + // Let's token is valid for another 10 minutes, then it reside 10 mintues in the blocklist + go addToBlocklist(c.Request, c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{})) + + return c.RenderJson(map[string]string{ + "id": "success", + "message": "Successfully logged out", + }) +} + +// Private methods +func (c *Auth) parseUserInfo() (*models.User, error) { + rUser := &models.User{} + decoder := json.NewDecoder(c.Request.Body) + err := decoder.Decode(rUser) + return rUser, err +} + +func addToBlocklist(r *revel.Request, claims map[string]interface{}) { + tokenString := jwt.GetAuthToken(r) + expriyAt := time.Minute * time.Duration(jwt.TokenRemainingValidity(claims[jwt.EXPIRATION_KEY])) + + cache.Set(tokenString, tokenString, expriyAt) +} diff --git a/jwtauth/app/jwt/jwt.go b/jwtauth/app/jwt/jwt.go new file mode 100644 index 0000000..ed856b5 --- /dev/null +++ b/jwtauth/app/jwt/jwt.go @@ -0,0 +1,276 @@ +package jwt + +import ( + "bufio" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "net/http" + "os" + "regexp" + "strings" + "time" + + "github.com/revel/revel" + "github.com/revel/revel/cache" + "gopkg.in/dgrijalva/jwt-go.v2" +) + +const ( + ISSUER_KEY = "iss" + ISSUED_AT_KEY = "iat" + EXPIRATION_KEY = "exp" + SUBJECT_KEY = "sub" + EXPIRE_OFFSET = 3600 + TOKEN_CLAIMS_KEY = "jwt.auth.claims" +) + +// Objects implementing the AuthHandler interface can be +// registered to Authenticate User for application +type AuthHandler interface { + Authenticate(username, password string) (string, bool) +} + +// The AuthHandlerFunc type is an adapter to allow the use of +// ordinary functions as Auth handlers. +type AuthHandlerFunc func(string, string) (string, bool) + +// Authenticate calls f(u, p). +func (f AuthHandlerFunc) Authenticate(u, p string) (string, bool) { + return f(u, p) +} + +var ( + Realm string + issuer string + privateKey *rsa.PrivateKey + publicKey *rsa.PublicKey + expiration int // in minutues + isIssuerExists bool + handler AuthHandler + anonymousPaths *regexp.Regexp +) + +/* +Method Init initializes JWT auth provider based on given config values from app.conf + auth.jwt.realm.name = "REVEL-JWT-AUTH" + auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values + auth.jwt.expiration = 30 // In minutes + auth.jwt.key.private = "/Users/jeeva/private.rsa" + auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" + auth.jwt.anonymous = "/token, /free/.*" // Valid regexp allowed for path +*/ +func Init(authHandler interface{}) { + Realm = revel.Config.StringDefault("auth.jwt.realm.name", "REVEL-JWT-AUTH") + issuer = revel.Config.StringDefault("auth.jwt.issuer", "") + expiration = revel.Config.IntDefault("auth.jwt.expiration", 60) // Default 60 minutes + anonymous := revel.Config.StringDefault("auth.jwt.anonymous", "/token") + + privateKeyPath, found := revel.Config.String("auth.jwt.key.private") + if !found { + revel.ERROR.Fatal("No auth.jwt.key.private found.") + } + + publicKeyPath, found := revel.Config.String("auth.jwt.key.public") + if !found { + revel.ERROR.Fatal("No auth.jwt.key.public found.") + } + + if _, ok := authHandler.(AuthHandler); !ok { + revel.ERROR.Fatal("Auth Handler doesn't implement interface jwt.AuthenticationHandler") + } + + Realm = fmt.Sprintf(`Bearer realm="%s"`, Realm) + + // preparing anonymous path regex + paths := strings.Split(anonymous, ",") + regexString := "" + for _, p := range paths { + regexString = fmt.Sprintf("%s^%s$|", regexString, strings.TrimSpace(p)) + } + anonymousPaths = regexp.MustCompile(regexString[:len(regexString)-1]) + + isIssuerExists = len(issuer) > 0 + handler = authHandler.(AuthHandler) + privateKey = loadPrivateKey(privateKeyPath) + publicKey = loadPublicKey(publicKeyPath) +} + +// Method GenerateToken creates JWT signed string with given subject value +func GenerateToken(subject string) (string, error) { + token := jwt.New(jwt.SigningMethodRS512) + + if isIssuerExists { + token.Claims[ISSUER_KEY] = issuer + } + + token.Claims[ISSUED_AT_KEY] = time.Now().Unix() + token.Claims[EXPIRATION_KEY] = time.Now().Add(time.Minute * time.Duration(expiration)).Unix() + token.Claims[SUBJECT_KEY] = subject + + tokenString, err := token.SignedString(privateKey) + if err != nil { + revel.ERROR.Printf("Generate token error [%v]", err) + return "", err + } + + return tokenString, nil +} + +// Method ParseFromRequest retrives JWT token, validates against SigningMethod & Issuer +// then returns *jwt.Token object +func ParseFromRequest(req *http.Request) (*jwt.Token, error) { + return jwt.ParseFromRequest(req, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + if token.Claims[ISSUER_KEY] != issuer { + return nil, fmt.Errorf("Unexpected token Issuer: %v", token.Claims[ISSUER_KEY]) + } + + return publicKey, nil + }) +} + +// Method TokenRemainingValidity calculates the remaining time left out in auth token +func TokenRemainingValidity(timestamp interface{}) int { + if validity, ok := timestamp.(float64); ok { + tm := time.Unix(int64(validity), 0) + remainer := tm.Sub(time.Now()) + if remainer > 0 { + return int(remainer.Seconds() + EXPIRE_OFFSET) + } + } + + return EXPIRE_OFFSET +} + +func Authenticate(username, password string) (string, bool) { + return handler.Authenticate(username, password) +} + +// Method GetAuthToken retrives Auth Token from revel.Request +// Authorization: Bearer +func GetAuthToken(req *revel.Request) string { + authToken := req.Header.Get("Authorization") + + if len(authToken) > 7 { // char count "Bearer " ==> 7 + return authToken[7:] + } + + return "" +} + +// Method IsInBlocklist is checks against logged out tokens +func IsInBlocklist(token string) bool { + var existingToken string + cache.Get(token, &existingToken) + + if len(existingToken) > 0 { + revel.WARN.Printf("Yes, blocklisted token [%v]", existingToken) + return true + } + + return false +} + +/* +Filter AuthFilter is Revel Filter for JWT Auth Token verification +Register it in the revel.Filters in /app/init.go + +Add jwt.AuthFilter anywhere demeed appropriate, it must be register after revel.PanicFilter + revel.Filters = []revel.Filter{ + revel.PanicFilter, + ... + jwt.AuthFilter, // JWT Auth Token verification for Request Paths + ... + } + +Note: If everything looks good then Claims map made available via c.Args +and can be accessed using c.Args[jwt.TOKEN_CLAIMS_KEY] +*/ +func AuthFilter(c *revel.Controller, fc []revel.Filter) { + if !anonymousPaths.MatchString(c.Request.URL.Path) { + token, err := ParseFromRequest(c.Request.Request) + if err == nil && token.Valid && !IsInBlocklist(GetAuthToken(c.Request)) { + c.Args[TOKEN_CLAIMS_KEY] = token.Claims + + fc[0](c, fc[1:]) // everything looks good, move on + } else { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + revel.ERROR.Println("That's not even a token") + } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { + revel.ERROR.Println("Timing is everything, Token is either expired or not active yet") + } else { + revel.ERROR.Printf("Couldn't handle this token: %v", err) + } + } else { + revel.ERROR.Printf("Couldn't handle this token: %v", err) + } + + c.Response.Status = http.StatusUnauthorized + c.Response.Out.Header().Add("WWW-Authenticate", Realm) + c.Result = c.RenderJson(map[string]string{ + "id": "unauthorized", + "message": "Invalid or token is not provided", + }) + + return + } + } + + fc[0](c, fc[1:]) //not applying JWT auth filter due to anonymous path +} + +// Private Methods +func loadPrivateKey(keyPath string) *rsa.PrivateKey { + keyData := readKeyFile(keyPath) + + privateKeyImported, err := x509.ParsePKCS1PrivateKey(keyData.Bytes) + if err != nil { + revel.ERROR.Fatalf("Private key import error [%v]", keyPath) + } + + return privateKeyImported +} + +func loadPublicKey(keyPath string) *rsa.PublicKey { + keyData := readKeyFile(keyPath) + + publicKeyImported, err := x509.ParsePKIXPublicKey(keyData.Bytes) + if err != nil { + revel.ERROR.Fatalf("Public key import error [%v]", keyPath) + } + + rsaPublicKey, ok := publicKeyImported.(*rsa.PublicKey) + if !ok { + revel.ERROR.Fatalf("Public key assert error [%v]", keyPath) + } + + return rsaPublicKey +} + +func readKeyFile(keyPath string) *pem.Block { + keyFile, err := os.Open(keyPath) + defer keyFile.Close() + if err != nil { + revel.ERROR.Fatalf("Key file open error [%v]", keyPath) + } + + pemFileInfo, _ := keyFile.Stat() + var size int64 = pemFileInfo.Size() + pemBytes := make([]byte, size) + + buffer := bufio.NewReader(keyFile) + _, err = buffer.Read(pemBytes) + if err != nil { + revel.ERROR.Fatalf("Key file read error [%v]", keyPath) + } + + keyData, _ := pem.Decode([]byte(pemBytes)) + + return keyData +} diff --git a/jwtauth/app/models/user.go b/jwtauth/app/models/user.go new file mode 100644 index 0000000..e9483dc --- /dev/null +++ b/jwtauth/app/models/user.go @@ -0,0 +1,6 @@ +package models + +type User struct { + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/jwtauth/conf/routes b/jwtauth/conf/routes new file mode 100644 index 0000000..a011a07 --- /dev/null +++ b/jwtauth/conf/routes @@ -0,0 +1,4 @@ +# JWT Auth Routes +POST /token Auth.Token +GET /refresh-token Auth.RefreshToken +GET /logout Auth.Logout diff --git a/jwtauth/jwtauth.sublime-project b/jwtauth/jwtauth.sublime-project new file mode 100644 index 0000000..d6bf73d --- /dev/null +++ b/jwtauth/jwtauth.sublime-project @@ -0,0 +1,18 @@ +{ + "folders": + [ + { + "path": "." + } + ], + "settings": + { + "GoSublime": + { + "env": + { + "GOPATH": "$HOME/Documents/scm/revel:$GS_GOPATH" + } + } + } +} From 28d1ecf054965fc0a05fbb3d9dd1107b96360eea Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 29 Aug 2015 18:29:07 -0700 Subject: [PATCH 03/18] Added documentation in the readme --- jwtauth/README.md | 58 +++++++++++++++++++++++++++++++++++++++++- jwtauth/app/jwt/jwt.go | 3 ++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/jwtauth/README.md b/jwtauth/README.md index 968949f..01d75fc 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -1 +1,57 @@ -# JWT Auth module for Revel \ No newline at end of file +# JWT Auth Module for Revel Go Framework + +Pluggable and easy to use JWT auth module. + +### Module Configuration +```ini +auth.jwt.realm.name = "REVEL-JWT-AUTH" // default is REVEL-JWT-AUTH +auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values (string, URL), default is REVEL-JWT-AUTH +auth.jwt.expiration = 30 // In minutes, default is 60 minutes +auth.jwt.key.private = "/Users/jeeva/private.rsa" +auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" +auth.jwt.anonymous = "/token, /freepass/.*" // Valid regexp allowed for path +``` + +### Enabling Auth Module + +Add `module.jwtauth = github.com/jeevatkm/jwtauth` into `conf/app.conf` + +### Registering Auth Routes + +Add `module:jwtauth` into `conf/routes`. Auth modules enables following routes +```sh +# JWT Auth Routes +POST /token Auth.Token +GET /refresh-token Auth.RefreshToken +GET /logout Auth.Logout +``` + +### Registering Auth Filter + +Revel Filter for JWT Auth Token verification. Register it in the `revel.Filters` in `/app/init.go` + +```go +// Add jwt.AuthFilter anywhere deemed appropriate, it must be register after revel.PanicFilter +revel.Filters = []revel.Filter{ + revel.PanicFilter, + ... + jwt.AuthFilter, // JWT Auth Token verification for Request Paths + ... +} +// Note: If everything looks good then Claims map made available via c.Args +// and can be accessed using c.Args[jwt.TOKEN_CLAIMS_KEY] +``` + +### Register Auth Handler + +Auth handler is responsible for validate user and returning `Subject (aka sub)` value and success/failure boolean. It should comply [AuthHandler](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L31) interface or use raw func via [jwt.AuthHandlerFunc](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L37). +```go +revel.OnAppStart(func() { + jwt.Init(&MyAuth{}) + // OR + jwt.Init(jwt.AuthHandlerFunc(func(username, password string) (string, bool) { + revel.INFO.Printf("Username: %v, Password: %v", username, password) + return "This is my subject value from function", true + })) +}) +``` diff --git a/jwtauth/app/jwt/jwt.go b/jwtauth/app/jwt/jwt.go index ed856b5..8727720 100644 --- a/jwtauth/app/jwt/jwt.go +++ b/jwtauth/app/jwt/jwt.go @@ -180,7 +180,8 @@ func IsInBlocklist(token string) bool { Filter AuthFilter is Revel Filter for JWT Auth Token verification Register it in the revel.Filters in /app/init.go -Add jwt.AuthFilter anywhere demeed appropriate, it must be register after revel.PanicFilter +Add jwt.AuthFilter anywhere deemed appropriate, it must be register after revel.PanicFilter + revel.Filters = []revel.Filter{ revel.PanicFilter, ... From dd2567ed4dfd09d50322e985d35ba41e65376f12 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 29 Aug 2015 20:03:05 -0700 Subject: [PATCH 04/18] Readme update and refactoring --- jwtauth/README.md | 6 +++--- jwtauth/app/controllers/{auth.go => jwtauth.go} | 10 +++++----- jwtauth/conf/routes | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) rename jwtauth/app/controllers/{auth.go => jwtauth.go} (92%) diff --git a/jwtauth/README.md b/jwtauth/README.md index 01d75fc..aa8ab9f 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -21,9 +21,9 @@ Add `module.jwtauth = github.com/jeevatkm/jwtauth` into `conf/app.conf` Add `module:jwtauth` into `conf/routes`. Auth modules enables following routes ```sh # JWT Auth Routes -POST /token Auth.Token -GET /refresh-token Auth.RefreshToken -GET /logout Auth.Logout +POST /token JwtAuth.Token +GET /refresh-token JwtAuth.RefreshToken +GET /logout JwtAuth.Logout ``` ### Registering Auth Filter diff --git a/jwtauth/app/controllers/auth.go b/jwtauth/app/controllers/jwtauth.go similarity index 92% rename from jwtauth/app/controllers/auth.go rename to jwtauth/app/controllers/jwtauth.go index ab3f287..9af25fa 100644 --- a/jwtauth/app/controllers/auth.go +++ b/jwtauth/app/controllers/jwtauth.go @@ -12,11 +12,11 @@ import ( "github.com/revel/revel/cache" ) -type Auth struct { +type JwtAuth struct { *revel.Controller } -func (c *Auth) Token() revel.Result { +func (c *JwtAuth) Token() revel.Result { user, err := c.parseUserInfo() if err != nil { revel.ERROR.Printf("Unable to read user info %q", err) @@ -51,7 +51,7 @@ func (c *Auth) Token() revel.Result { }) } -func (c *Auth) RefreshToken() revel.Result { +func (c *JwtAuth) RefreshToken() revel.Result { claims := c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{}) revel.INFO.Printf("Claims: %q", claims) @@ -74,7 +74,7 @@ func (c *Auth) RefreshToken() revel.Result { }) } -func (c *Auth) Logout() revel.Result { +func (c *JwtAuth) Logout() revel.Result { // Auth token will be added to blocklist for remaining token validitity period // Let's token is valid for another 10 minutes, then it reside 10 mintues in the blocklist go addToBlocklist(c.Request, c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{})) @@ -86,7 +86,7 @@ func (c *Auth) Logout() revel.Result { } // Private methods -func (c *Auth) parseUserInfo() (*models.User, error) { +func (c *JwtAuth) parseUserInfo() (*models.User, error) { rUser := &models.User{} decoder := json.NewDecoder(c.Request.Body) err := decoder.Decode(rUser) diff --git a/jwtauth/conf/routes b/jwtauth/conf/routes index a011a07..d18c2ba 100644 --- a/jwtauth/conf/routes +++ b/jwtauth/conf/routes @@ -1,4 +1,4 @@ # JWT Auth Routes -POST /token Auth.Token -GET /refresh-token Auth.RefreshToken -GET /logout Auth.Logout +POST /token JwtAuth.Token +GET /refresh-token JwtAuth.RefreshToken +GET /logout JwtAuth.Logout From 397561111e35dd01914efa496390f13778cc20bf Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Mon, 7 Sep 2015 19:35:33 -0700 Subject: [PATCH 05/18] Improvements, refactor and readme update --- jwtauth/README.md | 57 +++++++++++++++++----- jwtauth/app/controllers/jwtauth.go | 14 +++--- jwtauth/app/jwt/jwt.go | 55 +++++++++++---------- jwtauth/app/models/{user.go => jwtuser.go} | 2 +- jwtauth/jwtauth.sublime-project | 2 +- 5 files changed, 83 insertions(+), 47 deletions(-) rename jwtauth/app/models/{user.go => jwtuser.go} (80%) diff --git a/jwtauth/README.md b/jwtauth/README.md index aa8ab9f..3ec7999 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -1,24 +1,46 @@ -# JWT Auth Module for Revel Go Framework +# JWT Auth Module for Revel Framework -Pluggable and easy to use JWT auth module. +Pluggable and easy to use JWT auth module in Revel Framework. + +Planning to bring following enhancement to this moudle: +* Choosing Signing Method (`HS*, RS*, ES*`) via config, currently module does `RS512` +* Module error messages via Revel messages `/messages/.en, etc` ### Module Configuration ```ini -auth.jwt.realm.name = "REVEL-JWT-AUTH" // default is REVEL-JWT-AUTH -auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values (string, URL), default is REVEL-JWT-AUTH -auth.jwt.expiration = 30 // In minutes, default is 60 minutes +# default is REVEL-JWT-AUTH +auth.jwt.realm.name = "REVEL-JWT-AUTH" + +# use appropriate values (string, URL), default is REVEL-JWT-AUTH +auth.jwt.issuer = "REVEL-JWT-AUTH" + +# In minutes, default is 60 minutes +auth.jwt.expiration = 30 + +# Secured Key auth.jwt.key.private = "/Users/jeeva/private.rsa" auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" -auth.jwt.anonymous = "/token, /freepass/.*" // Valid regexp allowed for path + +# Valid regexp allowed for path +auth.jwt.anonymous = "/token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" ``` ### Enabling Auth Module -Add `module.jwtauth = github.com/jeevatkm/jwtauth` into `conf/app.conf` +Add following into `conf/app.conf` revel app configuration +```ini +# Enabling JWT Auth module +module.jwtauth = github.com/jeevatkm/jwtauth +``` ### Registering Auth Routes -Add `module:jwtauth` into `conf/routes`. Auth modules enables following routes +Add following into `conf/routes`. +```sh +# Adding JWT Auth routes into application +module:jwtauth +``` +JWT Auth modules enables following routes- ```sh # JWT Auth Routes POST /token JwtAuth.Token @@ -39,7 +61,7 @@ revel.Filters = []revel.Filter{ ... } // Note: If everything looks good then Claims map made available via c.Args -// and can be accessed using c.Args[jwt.TOKEN_CLAIMS_KEY] +// and can be accessed using c.Args[jwt.TokenClaimsKey] ``` ### Register Auth Handler @@ -47,11 +69,22 @@ revel.Filters = []revel.Filter{ Auth handler is responsible for validate user and returning `Subject (aka sub)` value and success/failure boolean. It should comply [AuthHandler](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L31) interface or use raw func via [jwt.AuthHandlerFunc](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L37). ```go revel.OnAppStart(func() { - jwt.Init(&MyAuth{}) - // OR jwt.Init(jwt.AuthHandlerFunc(func(username, password string) (string, bool) { + + // This method will be invoked by JwtAuth module for authentication + // Call your implementation to authenticate user revel.INFO.Printf("Username: %v, Password: %v", username, password) - return "This is my subject value from function", true + + // .... + // .... + + // after successful authentication + // create User subject value, which you want to inculde in signed string + // such as User Id, user email address, etc. + + userId := 100001 + + return fmt.Sprintf("%d", userId), authenticated })) }) ``` diff --git a/jwtauth/app/controllers/jwtauth.go b/jwtauth/app/controllers/jwtauth.go index 9af25fa..2645c54 100644 --- a/jwtauth/app/controllers/jwtauth.go +++ b/jwtauth/app/controllers/jwtauth.go @@ -43,7 +43,7 @@ func (c *JwtAuth) Token() revel.Result { } c.Response.Status = http.StatusUnauthorized - c.Response.Out.Header().Set("WWW-Authenticate", jwt.Realm) + c.Response.Out.Header().Set("Www-Authenticate", jwt.Realm) return c.RenderJson(map[string]string{ "id": "unauthorized", @@ -52,10 +52,10 @@ func (c *JwtAuth) Token() revel.Result { } func (c *JwtAuth) RefreshToken() revel.Result { - claims := c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{}) + claims := c.Args[jwt.TokenClaimsKey].(map[string]interface{}) revel.INFO.Printf("Claims: %q", claims) - tokenString, err := jwt.GenerateToken(claims[jwt.SUBJECT_KEY].(string)) + tokenString, err := jwt.GenerateToken(claims[jwt.SubjectKey].(string)) if err != nil { c.Response.Status = http.StatusInternalServerError return c.RenderJson(map[string]string{ @@ -77,7 +77,7 @@ func (c *JwtAuth) RefreshToken() revel.Result { func (c *JwtAuth) Logout() revel.Result { // Auth token will be added to blocklist for remaining token validitity period // Let's token is valid for another 10 minutes, then it reside 10 mintues in the blocklist - go addToBlocklist(c.Request, c.Args[jwt.TOKEN_CLAIMS_KEY].(map[string]interface{})) + go addToBlocklist(c.Request, c.Args[jwt.TokenClaimsKey].(map[string]interface{})) return c.RenderJson(map[string]string{ "id": "success", @@ -86,8 +86,8 @@ func (c *JwtAuth) Logout() revel.Result { } // Private methods -func (c *JwtAuth) parseUserInfo() (*models.User, error) { - rUser := &models.User{} +func (c *JwtAuth) parseUserInfo() (*models.JwtUser, error) { + rUser := &models.JwtUser{} decoder := json.NewDecoder(c.Request.Body) err := decoder.Decode(rUser) return rUser, err @@ -95,7 +95,7 @@ func (c *JwtAuth) parseUserInfo() (*models.User, error) { func addToBlocklist(r *revel.Request, claims map[string]interface{}) { tokenString := jwt.GetAuthToken(r) - expriyAt := time.Minute * time.Duration(jwt.TokenRemainingValidity(claims[jwt.EXPIRATION_KEY])) + expriyAt := time.Minute * time.Duration(jwt.TokenRemainingValidity(claims[jwt.ExpirationKey])) cache.Set(tokenString, tokenString, expriyAt) } diff --git a/jwtauth/app/jwt/jwt.go b/jwtauth/app/jwt/jwt.go index 8727720..beb2e44 100644 --- a/jwtauth/app/jwt/jwt.go +++ b/jwtauth/app/jwt/jwt.go @@ -18,12 +18,12 @@ import ( ) const ( - ISSUER_KEY = "iss" - ISSUED_AT_KEY = "iat" - EXPIRATION_KEY = "exp" - SUBJECT_KEY = "sub" - EXPIRE_OFFSET = 3600 - TOKEN_CLAIMS_KEY = "jwt.auth.claims" + IssueKey = "iss" + IssuedAtKey = "iat" + ExpirationKey = "exp" + SubjectKey = "sub" + ExpireOffset = 3600 + TokenClaimsKey = "jwt.auth.claims" ) // Objects implementing the AuthHandler interface can be @@ -54,12 +54,14 @@ var ( /* Method Init initializes JWT auth provider based on given config values from app.conf - auth.jwt.realm.name = "REVEL-JWT-AUTH" - auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values - auth.jwt.expiration = 30 // In minutes + auth.jwt.realm.name = "REVEL-JWT-AUTH" // default is REVEL-JWT-AUTH + auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values (string, URL), default is REVEL-JWT-AUTH + auth.jwt.expiration = 30 // In minutes, default is 60 minutes auth.jwt.key.private = "/Users/jeeva/private.rsa" auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" - auth.jwt.anonymous = "/token, /free/.*" // Valid regexp allowed for path + + // Valid regexp allowed for path + auth.jwt.anonymous = "/token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" */ func Init(authHandler interface{}) { Realm = revel.Config.StringDefault("auth.jwt.realm.name", "REVEL-JWT-AUTH") @@ -78,7 +80,7 @@ func Init(authHandler interface{}) { } if _, ok := authHandler.(AuthHandler); !ok { - revel.ERROR.Fatal("Auth Handler doesn't implement interface jwt.AuthenticationHandler") + revel.ERROR.Fatal("Auth Handler doesn't implement interface jwt.AuthHandler") } Realm = fmt.Sprintf(`Bearer realm="%s"`, Realm) @@ -87,9 +89,10 @@ func Init(authHandler interface{}) { paths := strings.Split(anonymous, ",") regexString := "" for _, p := range paths { - regexString = fmt.Sprintf("%s^%s$|", regexString, strings.TrimSpace(p)) + regexString = fmt.Sprintf("%s%s|", regexString, strings.TrimSpace(p)) } - anonymousPaths = regexp.MustCompile(regexString[:len(regexString)-1]) + regexString = fmt.Sprintf("^(%s)", regexString[:len(regexString)-1]) + anonymousPaths = regexp.MustCompile(regexString) isIssuerExists = len(issuer) > 0 handler = authHandler.(AuthHandler) @@ -102,12 +105,12 @@ func GenerateToken(subject string) (string, error) { token := jwt.New(jwt.SigningMethodRS512) if isIssuerExists { - token.Claims[ISSUER_KEY] = issuer + token.Claims[IssueKey] = issuer } - token.Claims[ISSUED_AT_KEY] = time.Now().Unix() - token.Claims[EXPIRATION_KEY] = time.Now().Add(time.Minute * time.Duration(expiration)).Unix() - token.Claims[SUBJECT_KEY] = subject + token.Claims[IssuedAtKey] = time.Now().Unix() + token.Claims[ExpirationKey] = time.Now().Add(time.Minute * time.Duration(expiration)).Unix() + token.Claims[SubjectKey] = subject tokenString, err := token.SignedString(privateKey) if err != nil { @@ -126,8 +129,8 @@ func ParseFromRequest(req *http.Request) (*jwt.Token, error) { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } - if token.Claims[ISSUER_KEY] != issuer { - return nil, fmt.Errorf("Unexpected token Issuer: %v", token.Claims[ISSUER_KEY]) + if token.Claims[IssueKey] != issuer { + return nil, fmt.Errorf("Unexpected token Issuer: %v", token.Claims[IssueKey]) } return publicKey, nil @@ -140,11 +143,11 @@ func TokenRemainingValidity(timestamp interface{}) int { tm := time.Unix(int64(validity), 0) remainer := tm.Sub(time.Now()) if remainer > 0 { - return int(remainer.Seconds() + EXPIRE_OFFSET) + return int(remainer.Seconds() + ExpireOffset) } } - return EXPIRE_OFFSET + return ExpireOffset } func Authenticate(username, password string) (string, bool) { @@ -190,13 +193,13 @@ Add jwt.AuthFilter anywhere deemed appropriate, it must be register after revel. } Note: If everything looks good then Claims map made available via c.Args -and can be accessed using c.Args[jwt.TOKEN_CLAIMS_KEY] +and can be accessed using c.Args[jwt.TokenClaimsKey] */ func AuthFilter(c *revel.Controller, fc []revel.Filter) { if !anonymousPaths.MatchString(c.Request.URL.Path) { token, err := ParseFromRequest(c.Request.Request) if err == nil && token.Valid && !IsInBlocklist(GetAuthToken(c.Request)) { - c.Args[TOKEN_CLAIMS_KEY] = token.Claims + c.Args[TokenClaimsKey] = token.Claims fc[0](c, fc[1:]) // everything looks good, move on } else { @@ -206,14 +209,14 @@ func AuthFilter(c *revel.Controller, fc []revel.Filter) { } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { revel.ERROR.Println("Timing is everything, Token is either expired or not active yet") } else { - revel.ERROR.Printf("Couldn't handle this token: %v", err) + revel.ERROR.Printf("Couldn't handle this request: %v", err) } } else { - revel.ERROR.Printf("Couldn't handle this token: %v", err) + revel.ERROR.Printf("Couldn't handle this request: %v", err) } c.Response.Status = http.StatusUnauthorized - c.Response.Out.Header().Add("WWW-Authenticate", Realm) + c.Response.Out.Header().Add("Www-Authenticate", Realm) c.Result = c.RenderJson(map[string]string{ "id": "unauthorized", "message": "Invalid or token is not provided", diff --git a/jwtauth/app/models/user.go b/jwtauth/app/models/jwtuser.go similarity index 80% rename from jwtauth/app/models/user.go rename to jwtauth/app/models/jwtuser.go index e9483dc..685dbef 100644 --- a/jwtauth/app/models/user.go +++ b/jwtauth/app/models/jwtuser.go @@ -1,6 +1,6 @@ package models -type User struct { +type JwtUser struct { Username string `json:"username"` Password string `json:"password"` } diff --git a/jwtauth/jwtauth.sublime-project b/jwtauth/jwtauth.sublime-project index d6bf73d..3e40de7 100644 --- a/jwtauth/jwtauth.sublime-project +++ b/jwtauth/jwtauth.sublime-project @@ -11,7 +11,7 @@ { "env": { - "GOPATH": "$HOME/Documents/scm/revel:$GS_GOPATH" + "GOPATH": "$HOME/Documents/scm/go-workspace:$GS_GOPATH" } } } From 0600b12236cd9ecc5abb0a680e375840383876f5 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Mon, 7 Sep 2015 19:48:02 -0700 Subject: [PATCH 06/18] Added revel example application here /example --- jwtauth/README.md | 1 + jwtauth/example/jwtauth-example/.gitignore | 3 + jwtauth/example/jwtauth-example/README.md | 7 + .../jwtauth-example/app/controllers/app.go | 96 ++++++++++ jwtauth/example/jwtauth-example/app/init.go | 42 +++++ .../jwtauth-example/app/models/user.go | 11 ++ .../jwtauth-example/app/views/app/index.html | 1 + jwtauth/example/jwtauth-example/conf/app.conf | 178 ++++++++++++++++++ jwtauth/example/jwtauth-example/conf/routes | 22 +++ .../jwtauth-example.sublime-project | 18 ++ .../jwtauth-example/messages/sample.en | 7 + .../example/jwtauth-example/tests/apptest.go | 21 +++ 12 files changed, 407 insertions(+) create mode 100644 jwtauth/example/jwtauth-example/.gitignore create mode 100644 jwtauth/example/jwtauth-example/README.md create mode 100644 jwtauth/example/jwtauth-example/app/controllers/app.go create mode 100644 jwtauth/example/jwtauth-example/app/init.go create mode 100644 jwtauth/example/jwtauth-example/app/models/user.go create mode 100644 jwtauth/example/jwtauth-example/app/views/app/index.html create mode 100644 jwtauth/example/jwtauth-example/conf/app.conf create mode 100644 jwtauth/example/jwtauth-example/conf/routes create mode 100644 jwtauth/example/jwtauth-example/jwtauth-example.sublime-project create mode 100644 jwtauth/example/jwtauth-example/messages/sample.en create mode 100644 jwtauth/example/jwtauth-example/tests/apptest.go diff --git a/jwtauth/README.md b/jwtauth/README.md index 3ec7999..2eae37a 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -83,6 +83,7 @@ revel.OnAppStart(func() { // such as User Id, user email address, etc. userId := 100001 + authenticated := true // Auth success return fmt.Sprintf("%d", userId), authenticated })) diff --git a/jwtauth/example/jwtauth-example/.gitignore b/jwtauth/example/jwtauth-example/.gitignore new file mode 100644 index 0000000..dae67d0 --- /dev/null +++ b/jwtauth/example/jwtauth-example/.gitignore @@ -0,0 +1,3 @@ +test-results/ +tmp/ +routes/ diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md new file mode 100644 index 0000000..2ae43ae --- /dev/null +++ b/jwtauth/example/jwtauth-example/README.md @@ -0,0 +1,7 @@ +## JWT Auth Module Example + +Ready to see JWT Auth module in action via this example. + +### How to run? + + diff --git a/jwtauth/example/jwtauth-example/app/controllers/app.go b/jwtauth/example/jwtauth-example/app/controllers/app.go new file mode 100644 index 0000000..7ad18b5 --- /dev/null +++ b/jwtauth/example/jwtauth-example/app/controllers/app.go @@ -0,0 +1,96 @@ +package controllers + +import ( + "fmt" + + "github.com/jeevatkm/jwtauth-example/app/models" + "github.com/jeevatkm/jwtauth/app/jwt" + "github.com/revel/revel" +) + +// For demo purpose +var appUsers map[string]*models.User + +type App struct { + *revel.Controller +} + +func (c App) Index() revel.Result { + return c.Render() +} + +func (c *App) Register() revel.Result { + return c.RenderJson(map[string]string{ + "message": "You have reached REGISTER route via POST method", + }) +} + +func (c *App) ForgotPassword() revel.Result { + return c.RenderJson(map[string]string{ + "message": "You have reached FORGOT PASSWORD route via POST method", + }) +} + +func (c *App) ResetPassword() revel.Result { + return c.RenderJson(map[string]string{ + "message": "You have reached RESET PASSWORD route via POST method", + }) +} + +func init() { + // Creating couple of users for example application + appUsers = make(map[string]*models.User) + + appUsers["100001"] = &models.User{ + Id: 100001, + Email: "jeeva@myjeeva.com", + Password: "sample1", + FirstName: "Jeeva", + LastName: "M", + } + + appUsers["100002"] = &models.User{ + Id: 100001, + Email: "user1@myjeeva.com", + Password: "user1", + FirstName: "User", + LastName: "1", + } + + appUsers["100003"] = &models.User{ + Id: 100001, + Email: "user2@myjeeva.com", + Password: "user2", + FirstName: "User", + LastName: "2", + } + + revel.OnAppStart(func() { + jwt.Init(jwt.AuthHandlerFunc(func(username, password string) (string, bool) { + + // This method will be invoked by JwtAuth module for authentication + // Call your implementation to authenticate user + revel.INFO.Printf("Username: %v, Password: %v", username, password) + + // .... + // .... + + // after successful authentication + // create User subject value, which you want to inculde in signed string + // such as User Id, user email address, etc. + + // Note: using plain password for demo purpose + var userId int64 + var authenticated bool + for _, v := range appUsers { + if v.Email == username && v.Password == password { + userId = v.Id + authenticated = true + break + } + } + + return fmt.Sprintf("%d", userId), authenticated + })) + }) +} diff --git a/jwtauth/example/jwtauth-example/app/init.go b/jwtauth/example/jwtauth-example/app/init.go new file mode 100644 index 0000000..664351b --- /dev/null +++ b/jwtauth/example/jwtauth-example/app/init.go @@ -0,0 +1,42 @@ +package app + +import ( + "github.com/jeevatkm/jwtauth/app/jwt" + "github.com/revel/revel" +) + +func init() { + // Filters is the default set of global filters. + revel.Filters = []revel.Filter{ + revel.PanicFilter, // Recover from panics and display an error page instead. + revel.RouterFilter, // Use the routing table to select the right Action + revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters. + revel.ParamsFilter, // Parse parameters into Controller.Params. + jwt.AuthFilter, // JWT Auth Token verification for Request Paths + revel.SessionFilter, // Restore and write the session cookie. + revel.FlashFilter, // Restore and write the flash cookie. + revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie. + revel.I18nFilter, // Resolve the requested language + HeaderFilter, // Add some security based headers + revel.InterceptorFilter, // Run interceptors around the action. + revel.CompressFilter, // Compress the result. + revel.ActionInvoker, // Invoke the action. + } + + // register startup functions with OnAppStart + // ( order dependent ) + // revel.OnAppStart(InitDB) + // revel.OnAppStart(FillCache) +} + +// TODO turn this into revel.HeaderFilter +// should probably also have a filter for CSRF +// not sure if it can go in the same filter or not +var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) { + // Add some common security headers + c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN") + c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block") + c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff") + + fc[0](c, fc[1:]) // Execute the next filter stage. +} diff --git a/jwtauth/example/jwtauth-example/app/models/user.go b/jwtauth/example/jwtauth-example/app/models/user.go new file mode 100644 index 0000000..70286b0 --- /dev/null +++ b/jwtauth/example/jwtauth-example/app/models/user.go @@ -0,0 +1,11 @@ +package models + +type User struct { + Id int64 + Email string + Password string + FirstName string + LastName string + + // so on... +} diff --git a/jwtauth/example/jwtauth-example/app/views/app/index.html b/jwtauth/example/jwtauth-example/app/views/app/index.html new file mode 100644 index 0000000..5a1c3bb --- /dev/null +++ b/jwtauth/example/jwtauth-example/app/views/app/index.html @@ -0,0 +1 @@ +

Welcome to JWT Auth Example

\ No newline at end of file diff --git a/jwtauth/example/jwtauth-example/conf/app.conf b/jwtauth/example/jwtauth-example/conf/app.conf new file mode 100644 index 0000000..6f40871 --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/app.conf @@ -0,0 +1,178 @@ +################################################################################ +# Revel configuration file +# See: +# http://revel.github.io/manual/appconf.html +# for more detailed documentation. +################################################################################ + +# This sets the `AppName` variable which can be used in your code as +# `if revel.AppName {...}` +app.name = jwtauth-example + +# A secret string which is passed to cryptographically sign the cookie to prevent +# (and detect) user modification. +# Keep this string secret or users will be able to inject arbitrary cookie values +# into your application +app.secret = 5HX7DyJHNI3PJDEt6EbUx9sZ5kbSSJUhs8rm1S592yoYAnHb28gmucIcDDRrsYQa + + +# The IP address on which to listen. +http.addr = + +# The port on which to listen. +http.port = 9000 + +# Whether to use SSL or not. +http.ssl = false + +# Path to an X509 certificate file, if using SSL. +#http.sslcert = + +# Path to an X509 certificate key, if using SSL. +#http.sslkey = + + +# For any cookies set by Revel (Session,Flash,Error) these properties will set +# the fields of: +# http://golang.org/pkg/net/http/#Cookie +# +# The HttpOnly attribute is supported by most modern browsers. On a supported +# browser, an HttpOnly session cookie will be used only when transmitting HTTP +# (or HTTPS) requests, thus restricting access from other, non-HTTP APIs (such +# as JavaScript). This restriction mitigates, but does not eliminate the threat +# of session cookie theft via cross-site scripting (XSS). This feature applies +# only to session-management cookies, and not other browser cookies. +cookie.httponly = false + +# Each cookie set by Revel is prefixed with this string. +cookie.prefix = REVEL + +# A secure cookie has the secure attribute enabled and is only used via HTTPS, +# ensuring that the cookie is always encrypted when transmitting from client to +# server. This makes the cookie less likely to be exposed to cookie theft via +# eavesdropping. +cookie.secure = false + +# Limit cookie access to a given domain +#cookie.domain = + +# Define when your session cookie expires. Possible values: +# "720h" +# A time duration (http://golang.org/pkg/time/#ParseDuration) after which +# the cookie expires and the session is invalid. +# "session" +# Sets a session cookie which invalidates the session when the user close +# the browser. +session.expires = 720h + + +# The date format used by Revel. Possible formats defined by the Go `time` +# package (http://golang.org/pkg/time/#Parse) +format.date = 01/02/2006 +format.datetime = 01/02/2006 15:04 + + +# Determines whether the template rendering should use chunked encoding. +# Chunked encoding can decrease the time to first byte on the client side by +# sending data before the entire template has been fully rendered. +results.chunked = false + + +# Prefixes for each log message line +log.trace.prefix = "TRACE " +log.info.prefix = "INFO " +log.warn.prefix = "WARN " +log.error.prefix = "ERROR " + + +# The default language of this application. +i18n.default_language = en + + +# Module to serve static content such as CSS, JavaScript and Media files +# Allows Routes like this: +# `Static.ServeModule("modulename","public")` +module.static=github.com/revel/modules/static + +# Enabling JWT Auth module +module.jwtauth = github.com/jeevatkm/jwtauth + + + +################################################################################ +# Section: dev +# This section is evaluated when running Revel in dev mode. Like so: +# `revel run path/to/myapp` +[dev] +# This sets `DevMode` variable to `true` which can be used in your code as +# `if revel.DevMode {...}` +# or in your templates with +# `` +mode.dev = true + +# default is REVEL-JWT-AUTH +auth.jwt.realm.name = "JWT-API-AUTH" + +# use appropriate values (string, URL), default is REVEL-JWT-AUTH +auth.jwt.issuer = "JWT API AUTH" + +# In minutes, default is 60 minutes +auth.jwt.expiration = 30 + +# Secured Key +auth.jwt.key.private = "/Users/madanj01/private.rsa" +auth.jwt.key.public = "/Users/madanj01/public.rsa.pub" + +# Valid regexp allowed for path +auth.jwt.anonymous = "/, /token, /register, /(forgot|reset)-password" + +# Pretty print JSON/XML when calling RenderJson/RenderXml +results.pretty = true + + +# Automatically watches your applicaton files and recompiles on-demand +watch = true + + +# If you set watcher.mode = "eager", the server starts to recompile +# your application every time your application's files change. +watcher.mode = "normal" + + +# Module to run code tests in the browser +# See: +# http://revel.github.io/manual/testing.html +module.testrunner = github.com/revel/modules/testrunner + + +# Where to log the various Revel logs +log.trace.output = off +log.info.output = stderr +log.warn.output = stderr +log.error.output = stderr + + + +################################################################################ +# Section: prod +# This section is evaluated when running Revel in production mode. Like so: +# `revel run path/to/myapp prod` +# See: +# [dev] section for documentation of the various settings +[prod] +mode.dev = false + + +results.pretty = false + + +watch = false + + +module.testrunner = + + +log.trace.output = off +log.info.output = off +log.warn.output = %(app.name)s.log +log.error.output = %(app.name)s.log diff --git a/jwtauth/example/jwtauth-example/conf/routes b/jwtauth/example/jwtauth-example/conf/routes new file mode 100644 index 0000000..7453471 --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/routes @@ -0,0 +1,22 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +module:testrunner + +# Adding JWT Auth routes into application +module:jwtauth + +GET / App.Index +POST /register App.Register +POST /forgot-password App.ForgotPassword +POST /reset-password App.ResetPassword + +# Ignore favicon requests +GET /favicon.ico 404 + +# Map static resources from the /app/public folder to the /public path +GET /public/*filepath Static.Serve("public") + +# Catch all +* /:controller/:action :controller.:action diff --git a/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project b/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project new file mode 100644 index 0000000..3e40de7 --- /dev/null +++ b/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project @@ -0,0 +1,18 @@ +{ + "folders": + [ + { + "path": "." + } + ], + "settings": + { + "GoSublime": + { + "env": + { + "GOPATH": "$HOME/Documents/scm/go-workspace:$GS_GOPATH" + } + } + } +} diff --git a/jwtauth/example/jwtauth-example/messages/sample.en b/jwtauth/example/jwtauth-example/messages/sample.en new file mode 100644 index 0000000..fc447f9 --- /dev/null +++ b/jwtauth/example/jwtauth-example/messages/sample.en @@ -0,0 +1,7 @@ +# Sample messages file for the English language (en) +# Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) +# Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) +# See also: +# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt +# - http://www.w3.org/International/questions/qa-accept-lang-locales + diff --git a/jwtauth/example/jwtauth-example/tests/apptest.go b/jwtauth/example/jwtauth-example/tests/apptest.go new file mode 100644 index 0000000..e81ca3d --- /dev/null +++ b/jwtauth/example/jwtauth-example/tests/apptest.go @@ -0,0 +1,21 @@ +package tests + +import "github.com/revel/revel/testing" + +type AppTest struct { + testing.TestSuite +} + +func (t *AppTest) Before() { + println("Set up") +} + +func (t *AppTest) TestThatIndexPageWorks() { + t.Get("/") + t.AssertOk() + t.AssertContentType("text/html; charset=utf-8") +} + +func (t *AppTest) After() { + println("Tear down") +} From f115e3d9c9f4d8c17254faa0c5ed7a948b52361f Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Mon, 7 Sep 2015 19:52:39 -0700 Subject: [PATCH 07/18] This file is just for `go get` option --- jwtauth/example/jwtauth-example/goget.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 jwtauth/example/jwtauth-example/goget.go diff --git a/jwtauth/example/jwtauth-example/goget.go b/jwtauth/example/jwtauth-example/goget.go new file mode 100644 index 0000000..b26caa4 --- /dev/null +++ b/jwtauth/example/jwtauth-example/goget.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("This file is just for go get option :)") +} From e8024fdf6973a5b3b9b7619f1c63c9d61001f6dc Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Mon, 7 Sep 2015 19:54:17 -0700 Subject: [PATCH 08/18] Delete goget.go --- jwtauth/example/jwtauth-example/goget.go | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 jwtauth/example/jwtauth-example/goget.go diff --git a/jwtauth/example/jwtauth-example/goget.go b/jwtauth/example/jwtauth-example/goget.go deleted file mode 100644 index b26caa4..0000000 --- a/jwtauth/example/jwtauth-example/goget.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "fmt" -) - -func main() { - fmt.Println("This file is just for go get option :)") -} From 9d43609cb21637d1ce2cc7c196972a26a526023b Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Mon, 7 Sep 2015 20:10:02 -0700 Subject: [PATCH 09/18] Added notes for example --- jwtauth/example/jwtauth-example/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md index 2ae43ae..a7c8569 100644 --- a/jwtauth/example/jwtauth-example/README.md +++ b/jwtauth/example/jwtauth-example/README.md @@ -1,7 +1,14 @@ -## JWT Auth Module Example +## JWT Auth Module Demo Revel Application -Ready to see JWT Auth module in action via this example. +Ready to see JWT Auth module in action. It demonstrates the JWT Auth Module usage. ### How to run? +```sh +$ go get github.com/jeevatkm/jwtauth +$ mv src/github.com/jeevatkm/jwtauth/example/jwtauth-example src/github.com/jeevatkm/ +$ revel run github.com/jeevatkm/jwtauth-example + +# now application will be running http://localhost:9000 +``` From 83647112ddc40df82386205574dca0114680b73b Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Tue, 8 Sep 2015 08:23:12 -0700 Subject: [PATCH 10/18] Checked-in key files too for demo app. however create one for yourself. --- jwtauth/example/jwtauth-example/conf/app.conf | 4 +-- .../example/jwtauth-example/conf/private.rsa | 27 +++++++++++++++++++ .../jwtauth-example/conf/public.rsa.pub | 9 +++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 jwtauth/example/jwtauth-example/conf/private.rsa create mode 100644 jwtauth/example/jwtauth-example/conf/public.rsa.pub diff --git a/jwtauth/example/jwtauth-example/conf/app.conf b/jwtauth/example/jwtauth-example/conf/app.conf index 6f40871..be98d1c 100644 --- a/jwtauth/example/jwtauth-example/conf/app.conf +++ b/jwtauth/example/jwtauth-example/conf/app.conf @@ -120,8 +120,8 @@ auth.jwt.issuer = "JWT API AUTH" auth.jwt.expiration = 30 # Secured Key -auth.jwt.key.private = "/Users/madanj01/private.rsa" -auth.jwt.key.public = "/Users/madanj01/public.rsa.pub" +auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/private.rsa" +auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/public.rsa.pub" # Valid regexp allowed for path auth.jwt.anonymous = "/, /token, /register, /(forgot|reset)-password" diff --git a/jwtauth/example/jwtauth-example/conf/private.rsa b/jwtauth/example/jwtauth-example/conf/private.rsa new file mode 100644 index 0000000..4a1e7c2 --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/private.rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0P3cWR1+vkiXJlrgm3gejMNde62BB/Q4anwdaQ3sTx+E4r3C +iXV27bYcTibYaxJTLrtuRvjpADAplEYsbkBeSAymWEkdUoyRmveUTLBqj60nS8jo +GEpg0QgJ+wLudeaHNsuz/BNHCu/HY+hlZorp0doxt7/mp+vW9+Ii7tGrsRHhlhUj +QVCPWhabPIl9SJERQL49XwU3S9z233GSlt8XRkvhr8YQIVvQOMNccaKkzBeFqORu +JCZ00LvyNZgKfQ+evJ//2C11M/KScx7U/SvOOWP0Ym10GZuSF6jyZZ9o2fGInEvo +Ij14VNgFKtQC4Kl7NVY+pzhGr+EFl9UeaDoplwIDAQABAoIBAA7OXWUG3OrYM7Uo +7Q62pNtuH9paQXDx0Wlh36eIr/wvDHgP349jfgh7RWgYAm8bfj8qUja+/argvqFd +k1pAPy21j7djfqtRgCNNdPk16mbBaq5IzoCiDFfizOo2m/RIX733EopCR18z+5lN +ZpmsL8KJRcpx0wKEh9dJ8xWeTx6dN9A/VWQRzVy7NtOwvdWf3cJzZNLM08Xts2Ad +gBh59M/dRSAHlojHOJ+7mM3JP8OFYUqRG8ihfWKzaWzu+1bSnOKL0/tc2XXeiMCF +N7r5Ym1UEVAcub+8j4UFc6FXTS1sEAJj+Idrh20TCmBGvs2hWhMcPNBV0R8RkkBI +ZIE6vwkCgYEA+oNn77qub8uCESaqvBn1Li+becai5B4BnivAV1UBX7lKsAgDvAwL +u0qOuLEDxrdGMy7movP9B6fs9qP7lkMno/vA+VtbmfvIVQ1Tl8jJus+snKkLbF8u +bZDx5n5HEXT/uduUDMxCCb46WYdX1qXFaEx0wnnmxVG5ABcmPkZhlbMCgYEA1ZGl +/5CaNwQLfmbVrulfqA2ZPOKsLTQPp47B7SE45bXNJTum1tjzmGELF5LaqRuJUuup +TgrDloBge004Fw5LrtJBThmHGcKO/t6ZqEUHMDXOIkll5b0OSxcCcAsh8qy4Knm0 +AGveRetV24j1I3FNZr9KXAAuhgFu6OUNOeprco0CgYEAwFu0rITpOtjGmArb4TIB +bSSLOvfGzmkoDt9Dgwu30VwDOKX+0B9jxr3aV4E9CBJk6hpiaM/3BDDyqPSD0/7e +6nD+3bpD3TpTutNP0+YO2M5smaLILb/sc59vz/A4+/OeBYXQ6f7R2o9iWKqvTRff +PFYw9cAK7orxBlvANuNuPTcCgYEAjA4B0EEiAOY0K2aAxz3gLzMLxPPZeaNkiLuD +zWA2Ed5RdBNUbBzGUq2BOqphnvih67EDzFweu7ngi7uuBuCnHTRhAziWcnw2jkmo +dsMd3a3LSozbt/dtQi0KujNyxdQiyigZtRUIJM4Z9egw6ldJLRJRT1gHKnYSJ8Te +EZb7c5kCgYEA8tw9PO5FrysNLt1X0rVTh/XtEYt8lPlcj1dufbyvU2Gsd4LeYSTc +I8fDQ86rHUbk7uDfzgA7h79UhuaMOapVNpeYlUn5FzkR/ossi8Oa3d5fj2yYPTBW +aSnO0r+ogmgVccO+KZvQcfJ+VmlhG8oRWhhkKmeAHDZv2zSTdaLQ2xI= +-----END RSA PRIVATE KEY----- diff --git a/jwtauth/example/jwtauth-example/conf/public.rsa.pub b/jwtauth/example/jwtauth-example/conf/public.rsa.pub new file mode 100644 index 0000000..320be95 --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/public.rsa.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0P3cWR1+vkiXJlrgm3ge +jMNde62BB/Q4anwdaQ3sTx+E4r3CiXV27bYcTibYaxJTLrtuRvjpADAplEYsbkBe +SAymWEkdUoyRmveUTLBqj60nS8joGEpg0QgJ+wLudeaHNsuz/BNHCu/HY+hlZorp +0doxt7/mp+vW9+Ii7tGrsRHhlhUjQVCPWhabPIl9SJERQL49XwU3S9z233GSlt8X +Rkvhr8YQIVvQOMNccaKkzBeFqORuJCZ00LvyNZgKfQ+evJ//2C11M/KScx7U/SvO +OWP0Ym10GZuSF6jyZZ9o2fGInEvoIj14VNgFKtQC4Kl7NVY+pzhGr+EFl9UeaDop +lwIDAQAB +-----END PUBLIC KEY----- From fda3d0c15631eddde870bdfaf5a8018a5b236e0e Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Tue, 8 Sep 2015 08:29:28 -0700 Subject: [PATCH 11/18] added commands in example readme for creating key files --- jwtauth/example/jwtauth-example/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md index a7c8569..d95412d 100644 --- a/jwtauth/example/jwtauth-example/README.md +++ b/jwtauth/example/jwtauth-example/README.md @@ -12,3 +12,12 @@ $ revel run github.com/jeevatkm/jwtauth-example # now application will be running http://localhost:9000 ``` + +### How to create Key files? +```sh +# generating private key file +$ openssl genrsa -out private.rsa 2048 + +# creating a public from private key we generated above +$ openssl rsa -in private.rsa -pubout > public.rsa.pub +``` \ No newline at end of file From 4ffb9834174c253d81f8318aeab2faaa8420b3ee Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Tue, 8 Sep 2015 17:11:01 -0700 Subject: [PATCH 12/18] Added ECDSA key files and app.conf update --- jwtauth/example/jwtauth-example/conf/app.conf | 27 +++++++++++++++---- .../jwtauth-example/conf/ec_private.pem | 6 +++++ .../jwtauth-example/conf/ec_public.pem | 5 ++++ .../conf/{private.rsa => rsa_private.pem} | 0 .../conf/{public.rsa.pub => rsa_public.pem} | 0 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 jwtauth/example/jwtauth-example/conf/ec_private.pem create mode 100644 jwtauth/example/jwtauth-example/conf/ec_public.pem rename jwtauth/example/jwtauth-example/conf/{private.rsa => rsa_private.pem} (100%) rename jwtauth/example/jwtauth-example/conf/{public.rsa.pub => rsa_public.pem} (100%) diff --git a/jwtauth/example/jwtauth-example/conf/app.conf b/jwtauth/example/jwtauth-example/conf/app.conf index be98d1c..4dd4782 100644 --- a/jwtauth/example/jwtauth-example/conf/app.conf +++ b/jwtauth/example/jwtauth-example/conf/app.conf @@ -110,18 +110,35 @@ module.jwtauth = github.com/jeevatkm/jwtauth # `` mode.dev = true +# +# JWT Auth Module configuration +# # default is REVEL-JWT-AUTH -auth.jwt.realm.name = "JWT-API-AUTH" +auth.jwt.realm.name = "JWT-AUTH" # use appropriate values (string, URL), default is REVEL-JWT-AUTH -auth.jwt.issuer = "JWT API AUTH" +auth.jwt.issuer = "JWT AUTH" # In minutes, default is 60 minutes auth.jwt.expiration = 30 -# Secured Key -auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/private.rsa" -auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/public.rsa.pub" +# Signing Method +# options are - HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 +auth.jwt.sign.method = "RS512" + +# RSA key files +# applicable to RS256, RS384, RS512 signing method and comment out others +auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_private.pem" +auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_public.pem" + +# Uncomment below two lines for ES256, ES384, ES512 signing method and comment out others +# since we will be use ECDSA +#auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_private.pem" +#auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_public.pem" + +# HMAC signing Secret value +# Uncomment below line for HS256, HS384, HS512 signing method and comment out others +#auth.jwt.key.hmac = "1A39B778C0CE40B1B32585CF846F61B1" # Valid regexp allowed for path auth.jwt.anonymous = "/, /token, /register, /(forgot|reset)-password" diff --git a/jwtauth/example/jwtauth-example/conf/ec_private.pem b/jwtauth/example/jwtauth-example/conf/ec_private.pem new file mode 100644 index 0000000..94b5abc --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/ec_private.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDAtao7AVreEqxzJSQ7tI9aai5r2mH9Lqv1XPH6oS6NyvPjNuIMyilkZ +GmUsKTnfOYegBwYFK4EEACKhZANiAAQz36OH0fGx4jb44EJ6q3DByXb10TzfHov7 +QDZESt/9+p0qfoGvgGlkGPr7ZamROrTuuZM2ezyVUF6Wlexzhnd84chrL9EvGEWI +Erz1EpstgMcuKObSkS8GGBkbNPev3Hw= +-----END EC PRIVATE KEY----- diff --git a/jwtauth/example/jwtauth-example/conf/ec_public.pem b/jwtauth/example/jwtauth-example/conf/ec_public.pem new file mode 100644 index 0000000..d96bf6a --- /dev/null +++ b/jwtauth/example/jwtauth-example/conf/ec_public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEM9+jh9HxseI2+OBCeqtwwcl29dE83x6L ++0A2RErf/fqdKn6Br4BpZBj6+2WpkTq07rmTNns8lVBelpXsc4Z3fOHIay/RLxhF +iBK89RKbLYDHLijm0pEvBhgZGzT3r9x8 +-----END PUBLIC KEY----- diff --git a/jwtauth/example/jwtauth-example/conf/private.rsa b/jwtauth/example/jwtauth-example/conf/rsa_private.pem similarity index 100% rename from jwtauth/example/jwtauth-example/conf/private.rsa rename to jwtauth/example/jwtauth-example/conf/rsa_private.pem diff --git a/jwtauth/example/jwtauth-example/conf/public.rsa.pub b/jwtauth/example/jwtauth-example/conf/rsa_public.pem similarity index 100% rename from jwtauth/example/jwtauth-example/conf/public.rsa.pub rename to jwtauth/example/jwtauth-example/conf/rsa_public.pem From 692adaa602a07bc71302e9d85afcd8107bec223f Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Tue, 8 Sep 2015 17:21:11 -0700 Subject: [PATCH 13/18] Implemented JWT signing methods - HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 --- jwtauth/README.md | 33 ++- jwtauth/app/jwt/jwt.go | 264 +++++++++++++++------- jwtauth/example/jwtauth-example/README.md | 21 +- 3 files changed, 228 insertions(+), 90 deletions(-) diff --git a/jwtauth/README.md b/jwtauth/README.md index 2eae37a..f9222e2 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -1,28 +1,43 @@ # JWT Auth Module for Revel Framework -Pluggable and easy to use JWT auth module in Revel Framework. +Pluggable and easy to use JWT auth module in Revel Framework. Module supports following JWT signing method `HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512`. Default is `RS512`. + +Include [example application](example/jwtauth-example) and it demostrates above mentioned JWT signing method(s). Planning to bring following enhancement to this moudle: -* Choosing Signing Method (`HS*, RS*, ES*`) via config, currently module does `RS512` * Module error messages via Revel messages `/messages/.en, etc` ### Module Configuration ```ini # default is REVEL-JWT-AUTH -auth.jwt.realm.name = "REVEL-JWT-AUTH" +auth.jwt.realm.name = "JWT-AUTH" # use appropriate values (string, URL), default is REVEL-JWT-AUTH -auth.jwt.issuer = "REVEL-JWT-AUTH" +auth.jwt.issuer = "JWT AUTH" # In minutes, default is 60 minutes auth.jwt.expiration = 30 -# Secured Key -auth.jwt.key.private = "/Users/jeeva/private.rsa" -auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" +# Signing Method +# options are - HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 +auth.jwt.sign.method = "RS512" + +# RSA key files +# applicable to RS256, RS384, RS512 signing method and comment out others +auth.jwt.key.private = "/Users/jeeva/rsa_private.pem" +auth.jwt.key.public = "/Users/jeeva/rsa_public.pem" + +# ECDSA key files +# Uncomment below two lines for ES256, ES384, ES512 signing method and comment out others +#auth.jwt.key.private = "/Users/jeeva/ec_private.pem" +#auth.jwt.key.public = "/Users/jeeva/ec_public.pem" + +# HMAC signing Secret value +# Uncomment below line for HS256, HS384, HS512 signing method and comment out others +#auth.jwt.key.hmac = "1A39B778C0CE40B1B32585CF846F61B1" # Valid regexp allowed for path -auth.jwt.anonymous = "/token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" +auth.jwt.anonymous = "/, /token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" ``` ### Enabling Auth Module @@ -64,7 +79,7 @@ revel.Filters = []revel.Filter{ // and can be accessed using c.Args[jwt.TokenClaimsKey] ``` -### Register Auth Handler +### Registering Auth Handler Auth handler is responsible for validate user and returning `Subject (aka sub)` value and success/failure boolean. It should comply [AuthHandler](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L31) interface or use raw func via [jwt.AuthHandlerFunc](https://github.com/jeevatkm/jwtauth/blob/master/app/jwt/jwt.go#L37). ```go diff --git a/jwtauth/app/jwt/jwt.go b/jwtauth/app/jwt/jwt.go index beb2e44..6935ded 100644 --- a/jwtauth/app/jwt/jwt.go +++ b/jwtauth/app/jwt/jwt.go @@ -1,13 +1,11 @@ package jwt import ( - "bufio" + "crypto/ecdsa" "crypto/rsa" - "crypto/x509" - "encoding/pem" "fmt" + "io/ioutil" "net/http" - "os" "regexp" "strings" "time" @@ -18,6 +16,7 @@ import ( ) const ( + Algorithm = "alg" IssueKey = "iss" IssuedAtKey = "iat" ExpirationKey = "exp" @@ -44,39 +43,129 @@ func (f AuthHandlerFunc) Authenticate(u, p string) (string, bool) { var ( Realm string issuer string - privateKey *rsa.PrivateKey - publicKey *rsa.PublicKey expiration int // in minutues isIssuerExists bool handler AuthHandler anonymousPaths *regexp.Regexp + signMethodName string + + // RS* signing elements + jwtAuthSignMethodRSA *jwt.SigningMethodRSA + rsaPrivateKey *rsa.PrivateKey + rsaPublicKey *rsa.PublicKey + + // ES* signing elements + jwtAuthSignMethodECDSA *jwt.SigningMethodECDSA + ecPrivatekey *ecdsa.PrivateKey + ecPublickey *ecdsa.PublicKey + + // HS* signing elements + jwtAuthSignMethodHMAC *jwt.SigningMethodHMAC + hmacSecretBytes []byte ) /* Method Init initializes JWT auth provider based on given config values from app.conf - auth.jwt.realm.name = "REVEL-JWT-AUTH" // default is REVEL-JWT-AUTH - auth.jwt.issuer = "REVEL-JWT-AUTH" // use appropriate values (string, URL), default is REVEL-JWT-AUTH - auth.jwt.expiration = 30 // In minutes, default is 60 minutes - auth.jwt.key.private = "/Users/jeeva/private.rsa" - auth.jwt.key.public = "/Users/jeeva/public.rsa.pub" + // + // JWT Auth Module configuration + // + // default is REVEL-JWT-AUTH + auth.jwt.realm.name = "JWT-AUTH" + + // use appropriate values (string, URL), default is REVEL-JWT-AUTH + auth.jwt.issuer = "JWT AUTH" + + // In minutes, default is 60 minutes + auth.jwt.expiration = 30 + + // Signing Method + // options are - HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 + auth.jwt.sign.method = "RS512" + + // RSA key files + // applicable to RS256, RS384, RS512 signing method and comment out others + auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_private.pem" + auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_public.pem" + + // Uncomment below two lines for ES256, ES384, ES512 signing method and comment out others + // since we will be use ECDSA + #auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_private.pem" + #auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_public.pem" + + // HMAC signing Secret value + // Uncomment below line for HS256, HS384, HS512 signing method and comment out others + #auth.jwt.key.hmac = "1A39B778C0CE40B1B32585CF846F61B1" // Valid regexp allowed for path - auth.jwt.anonymous = "/token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" + // Internal it will end up like this "^(/$|/token|/register|/(forgot|validate-reset|reset)-password)" + auth.jwt.anonymous = "/, /token, /register, /(forgot|validate-reset|reset)-password" */ func Init(authHandler interface{}) { + var ( + found bool + err error + ) Realm = revel.Config.StringDefault("auth.jwt.realm.name", "REVEL-JWT-AUTH") - issuer = revel.Config.StringDefault("auth.jwt.issuer", "") - expiration = revel.Config.IntDefault("auth.jwt.expiration", 60) // Default 60 minutes - anonymous := revel.Config.StringDefault("auth.jwt.anonymous", "/token") + issuer = revel.Config.StringDefault("auth.jwt.issuer", "REVEL-JWT-AUTH") + expiration = revel.Config.IntDefault("auth.jwt.expiration", 60) // Default 60 minutes + signMethodName = revel.Config.StringDefault("auth.jwt.sign.method", "RS512") //Default is RS512 + + if strings.HasPrefix(signMethodName, "RS") || strings.HasPrefix(signMethodName, "ES") { + var ( + privateKeyPath string + publicKeyPath string + ) + privateKeyPath, found = revel.Config.String("auth.jwt.key.private") + if !found { + revel.ERROR.Fatal("No auth.jwt.key.private found, it's required for RS*/ES* signing method.") + } + + publicKeyPath, found = revel.Config.String("auth.jwt.key.public") + if !found { + revel.ERROR.Fatal("No auth.jwt.key.public found, it's required for RS*/ES* signing method.") + } + + if strings.HasPrefix(signMethodName, "RS") { + // loading RSA key files + rsaPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(readFile(privateKeyPath)) + if err != nil { + revel.ERROR.Fatalf("Private key file read error [%v].", privateKeyPath) + } + + rsaPublicKey, err = jwt.ParseRSAPublicKeyFromPEM(readFile(publicKeyPath)) + if err != nil { + revel.ERROR.Fatalf("Public key file read error [%v].", publicKeyPath) + } + + // Signing Method + jwtAuthSignMethodRSA = identifySigningMethod().(*jwt.SigningMethodRSA) + } else if strings.HasPrefix(signMethodName, "ES") { + // loading ECDSA key files + ecPrivatekey, err = jwt.ParseECPrivateKeyFromPEM(readFile(privateKeyPath)) + if err != nil { + revel.ERROR.Fatalf("Private key file read error [%v].", privateKeyPath) + } + + ecPublickey, err = jwt.ParseECPublicKeyFromPEM(readFile(publicKeyPath)) + if err != nil { + revel.ERROR.Fatalf("Public key file read error [%v].", publicKeyPath) + } - privateKeyPath, found := revel.Config.String("auth.jwt.key.private") - if !found { - revel.ERROR.Fatal("No auth.jwt.key.private found.") + // Signing Method + jwtAuthSignMethodECDSA = identifySigningMethod().(*jwt.SigningMethodECDSA) + } } - publicKeyPath, found := revel.Config.String("auth.jwt.key.public") - if !found { - revel.ERROR.Fatal("No auth.jwt.key.public found.") + if strings.HasPrefix(signMethodName, "HS") { + var hmacSecertValue string + hmacSecertValue, found = revel.Config.String("auth.jwt.key.hmac") + if !found || len(strings.TrimSpace(hmacSecertValue)) == 0 { + revel.ERROR.Fatal("No auth.jwt.key.hmac found, it's required for HS* signing method.") + } + + // Signing Method + jwtAuthSignMethodHMAC = identifySigningMethod().(*jwt.SigningMethodHMAC) + hmacSecretBytes = []byte(hmacSecertValue) } if _, ok := authHandler.(AuthHandler); !ok { @@ -86,54 +175,65 @@ func Init(authHandler interface{}) { Realm = fmt.Sprintf(`Bearer realm="%s"`, Realm) // preparing anonymous path regex + anonymous := revel.Config.StringDefault("auth.jwt.anonymous", "/token") paths := strings.Split(anonymous, ",") regexString := "" for _, p := range paths { - regexString = fmt.Sprintf("%s%s|", regexString, strings.TrimSpace(p)) + if strings.TrimSpace(p) == "/" { // TODO Something not right, Might need a revist here + regexString = fmt.Sprintf("%s%s$|", regexString, strings.TrimSpace(p)) + } else { + regexString = fmt.Sprintf("%s%s|", regexString, strings.TrimSpace(p)) + } } regexString = fmt.Sprintf("^(%s)", regexString[:len(regexString)-1]) anonymousPaths = regexp.MustCompile(regexString) isIssuerExists = len(issuer) > 0 handler = authHandler.(AuthHandler) - privateKey = loadPrivateKey(privateKeyPath) - publicKey = loadPublicKey(publicKeyPath) + + revel.INFO.Printf("JWT Auth Module - Signing Method: %v", signMethodName) } // Method GenerateToken creates JWT signed string with given subject value func GenerateToken(subject string) (string, error) { - token := jwt.New(jwt.SigningMethodRS512) - - if isIssuerExists { - token.Claims[IssueKey] = issuer - } + token := createToken() + token.Claims[IssueKey] = issuer token.Claims[IssuedAtKey] = time.Now().Unix() token.Claims[ExpirationKey] = time.Now().Add(time.Minute * time.Duration(expiration)).Unix() token.Claims[SubjectKey] = subject - tokenString, err := token.SignedString(privateKey) - if err != nil { - revel.ERROR.Printf("Generate token error [%v]", err) - return "", err - } - - return tokenString, nil + return signString(token) } // Method ParseFromRequest retrives JWT token, validates against SigningMethod & Issuer // then returns *jwt.Token object func ParseFromRequest(req *http.Request) (*jwt.Token, error) { return jwt.ParseFromRequest(req, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + // Signing Method verification + // https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + var signMethodOk bool + var key interface{} + if strings.HasPrefix(signMethodName, "RS") { + _, signMethodOk = token.Method.(*jwt.SigningMethodRSA) + key = rsaPublicKey + } else if strings.HasPrefix(signMethodName, "HS") { + _, signMethodOk = token.Method.(*jwt.SigningMethodHMAC) + key = hmacSecretBytes + } else if strings.HasPrefix(signMethodName, "ES") { + _, signMethodOk = token.Method.(*jwt.SigningMethodECDSA) + key = ecPublickey + } + if !signMethodOk { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header[Algorithm]) } + // Issuer verification if token.Claims[IssueKey] != issuer { - return nil, fmt.Errorf("Unexpected token Issuer: %v", token.Claims[IssueKey]) + return nil, fmt.Errorf("Unexpected token issuer: %v", token.Claims[IssueKey]) } - return publicKey, nil + return key, nil }) } @@ -196,7 +296,9 @@ Note: If everything looks good then Claims map made available via c.Args and can be accessed using c.Args[jwt.TokenClaimsKey] */ func AuthFilter(c *revel.Controller, fc []revel.Filter) { - if !anonymousPaths.MatchString(c.Request.URL.Path) { + if anonymousPaths.MatchString(c.Request.URL.Path) { + fc[0](c, fc[1:]) //not applying JWT auth filter due to anonymous path + } else { token, err := ParseFromRequest(c.Request.Request) if err == nil && token.Valid && !IsInBlocklist(GetAuthToken(c.Request)) { c.Args[TokenClaimsKey] = token.Claims @@ -225,56 +327,66 @@ func AuthFilter(c *revel.Controller, fc []revel.Filter) { return } } - - fc[0](c, fc[1:]) //not applying JWT auth filter due to anonymous path } +// // Private Methods -func loadPrivateKey(keyPath string) *rsa.PrivateKey { - keyData := readKeyFile(keyPath) - - privateKeyImported, err := x509.ParsePKCS1PrivateKey(keyData.Bytes) - if err != nil { - revel.ERROR.Fatalf("Private key import error [%v]", keyPath) +// +func createToken() *jwt.Token { + if strings.HasPrefix(signMethodName, "HS") { + return jwt.New(jwtAuthSignMethodHMAC) + } else if strings.HasPrefix(signMethodName, "ES") { + return jwt.New(jwtAuthSignMethodECDSA) } - return privateKeyImported + return jwt.New(jwtAuthSignMethodRSA) } -func loadPublicKey(keyPath string) *rsa.PublicKey { - keyData := readKeyFile(keyPath) - - publicKeyImported, err := x509.ParsePKIXPublicKey(keyData.Bytes) - if err != nil { - revel.ERROR.Fatalf("Public key import error [%v]", keyPath) +func signString(token *jwt.Token) (signedString string, err error) { + switch signMethodName { + case "RS256", "RS384", "RS512": + signedString, err = token.SignedString(rsaPrivateKey) + case "HS256", "HS384", "HS512": + signedString, err = token.SignedString(hmacSecretBytes) + case "ES256", "ES384", "ES512": + signedString, err = token.SignedString(ecPrivatekey) } - rsaPublicKey, ok := publicKeyImported.(*rsa.PublicKey) - if !ok { - revel.ERROR.Fatalf("Public key assert error [%v]", keyPath) + if err != nil { + revel.ERROR.Printf("Generate token error [%v]", err) } - return rsaPublicKey + return } -func readKeyFile(keyPath string) *pem.Block { - keyFile, err := os.Open(keyPath) - defer keyFile.Close() +func readFile(file string) []byte { + bytes, err := ioutil.ReadFile(file) if err != nil { - revel.ERROR.Fatalf("Key file open error [%v]", keyPath) + revel.ERROR.Fatalf("Key file read error [%v]", file) } + return bytes +} - pemFileInfo, _ := keyFile.Stat() - var size int64 = pemFileInfo.Size() - pemBytes := make([]byte, size) - - buffer := bufio.NewReader(keyFile) - _, err = buffer.Read(pemBytes) - if err != nil { - revel.ERROR.Fatalf("Key file read error [%v]", keyPath) +func identifySigningMethod() (signingMethod interface{}) { + switch signMethodName { + case "RS256": + signingMethod = jwt.SigningMethodRS256 + case "RS384": + signingMethod = jwt.SigningMethodRS384 + case "RS512": + signingMethod = jwt.SigningMethodRS512 + case "HS256": + signingMethod = jwt.SigningMethodHS256 + case "HS384": + signingMethod = jwt.SigningMethodHS384 + case "HS512": + signingMethod = jwt.SigningMethodHS512 + case "ES256": + signingMethod = jwt.SigningMethodES256 + case "ES384": + signingMethod = jwt.SigningMethodES384 + case "ES512": + signingMethod = jwt.SigningMethodES512 } - - keyData, _ := pem.Decode([]byte(pemBytes)) - - return keyData + return } diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md index d95412d..bacced9 100644 --- a/jwtauth/example/jwtauth-example/README.md +++ b/jwtauth/example/jwtauth-example/README.md @@ -4,20 +4,31 @@ Ready to see JWT Auth module in action. It demonstrates the JWT Auth Module usag ### How to run? ```sh +$ cd $GOPATH + $ go get github.com/jeevatkm/jwtauth -$ mv src/github.com/jeevatkm/jwtauth/example/jwtauth-example src/github.com/jeevatkm/ +$ ln -s $GOPATH/src/github.com/jeevatkm/jwtauth/example/jwtauth-example $GOPATH/src/github.com/jeevatkm/jwtauth-example $ revel run github.com/jeevatkm/jwtauth-example # now application will be running http://localhost:9000 ``` -### How to create Key files? +### How to create RSA Key files? +```sh +# generating private key file +$ openssl genrsa -out rsa_private.pem 2048 + +# creating a public from private key we generated above +$ openssl rsa -in rsa_private.pem -pubout > rsa_public.pem +``` + +### How to create ECDSA Key files? ```sh # generating private key file -$ openssl genrsa -out private.rsa 2048 +$ openssl ecparam -name secp384r1 -genkey -noout -out ec_private.pem # creating a public from private key we generated above -$ openssl rsa -in private.rsa -pubout > public.rsa.pub -``` \ No newline at end of file +$ openssl ec -in ec_private.pem -pubout -out ec_public.pem +``` From acff165c8e092ff14d6894e0c899be3f8cca20d4 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Wed, 9 Sep 2015 16:46:14 -0700 Subject: [PATCH 14/18] Added sample application documentation --- jwtauth/example/jwtauth-example/README.md | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md index bacced9..8e7f70c 100644 --- a/jwtauth/example/jwtauth-example/README.md +++ b/jwtauth/example/jwtauth-example/README.md @@ -15,6 +15,57 @@ $ revel run github.com/jeevatkm/jwtauth-example # now application will be running http://localhost:9000 ``` +### Sample Application in action +JWT Auth Module provides following routes for Revel application. It is perfectly ready to use for REST API, Web application and Single Page web application. +```sh +# JWT Auth Routes +POST /token JwtAuth.Token +GET /refresh-token JwtAuth.RefreshToken +GET /logout JwtAuth.Logout +``` +Sample application has three user information, such as- +```sh +Username: jeeva@myjeeva.com +Password: sample1 + +Username: user1@myjeeva.com +Password: user1 + +Username: user2@myjeeva.com +Password: user2 +``` + +#### /token +Let's get the auth token, pick any rest client by your choice and user credentials from above. + +| Field | Value | +| ------------- | ------------- | +| URL | http://localhost:9000/token | +| Method | POST | +| Body | ` { "username":"jeeva@myjeeva.com", "password":"sample1" } ` | +| Response | ` { "token": "" } ` | + +#### /refresh-oken +Let's get refereshed auth token. **Once you execute this request, your previous auth token is no longer valid.** + +| Field | Value | +| ------------- | ------------- | +| URL |http://localhost:9000/refresh-token | +| Method | GET | +| Header | ` Authorization: Bearer ` | +| Response | ` { "token": "" } ` | + +#### /logout +I'm done, let's logout from application. + +| Field | Value | +| ------------- | ------------- | +| URL |http://localhost:9000/logout | +| Method | GET | +| Header | ` Authorization: Bearer ` | +| Response | ` { "id": "success", "message": "Successfully logged out" } ` | + + ### How to create RSA Key files? ```sh # generating private key file From 7e933edd1107c00745f21ff99ade39477033f99d Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 12 Sep 2015 18:44:08 -0700 Subject: [PATCH 15/18] clean up --- .../jwtauth-example.sublime-project | 18 ------------------ jwtauth/jwtauth.sublime-project | 18 ------------------ 2 files changed, 36 deletions(-) delete mode 100644 jwtauth/example/jwtauth-example/jwtauth-example.sublime-project delete mode 100644 jwtauth/jwtauth.sublime-project diff --git a/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project b/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project deleted file mode 100644 index 3e40de7..0000000 --- a/jwtauth/example/jwtauth-example/jwtauth-example.sublime-project +++ /dev/null @@ -1,18 +0,0 @@ -{ - "folders": - [ - { - "path": "." - } - ], - "settings": - { - "GoSublime": - { - "env": - { - "GOPATH": "$HOME/Documents/scm/go-workspace:$GS_GOPATH" - } - } - } -} diff --git a/jwtauth/jwtauth.sublime-project b/jwtauth/jwtauth.sublime-project deleted file mode 100644 index 3e40de7..0000000 --- a/jwtauth/jwtauth.sublime-project +++ /dev/null @@ -1,18 +0,0 @@ -{ - "folders": - [ - { - "path": "." - } - ], - "settings": - { - "GoSublime": - { - "env": - { - "GOPATH": "$HOME/Documents/scm/go-workspace:$GS_GOPATH" - } - } - } -} From a921dd1a0deac8ab9d82a974f80c6ad798f95fcd Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sat, 12 Sep 2015 18:48:11 -0700 Subject: [PATCH 16/18] Module and example application namespace updated (revel/modules) --- jwtauth/.addthis | 0 jwtauth/app/controllers/jwtauth.go | 4 ++-- jwtauth/example/jwtauth-example/README.md | 6 ++---- jwtauth/example/jwtauth-example/app/controllers/app.go | 4 ++-- jwtauth/example/jwtauth-example/app/init.go | 2 +- jwtauth/example/jwtauth-example/conf/app.conf | 10 +++++----- 6 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 jwtauth/.addthis diff --git a/jwtauth/.addthis b/jwtauth/.addthis deleted file mode 100644 index e69de29..0000000 diff --git a/jwtauth/app/controllers/jwtauth.go b/jwtauth/app/controllers/jwtauth.go index 2645c54..cf59a0f 100644 --- a/jwtauth/app/controllers/jwtauth.go +++ b/jwtauth/app/controllers/jwtauth.go @@ -5,8 +5,8 @@ import ( "net/http" "time" - "github.com/jeevatkm/jwtauth/app/jwt" - "github.com/jeevatkm/jwtauth/app/models" + "github.com/revel/modules/jwtauth/app/jwt" + "github.com/revel/modules/jwtauth/app/models" "github.com/revel/revel" "github.com/revel/revel/cache" diff --git a/jwtauth/example/jwtauth-example/README.md b/jwtauth/example/jwtauth-example/README.md index 8e7f70c..aa9f16e 100644 --- a/jwtauth/example/jwtauth-example/README.md +++ b/jwtauth/example/jwtauth-example/README.md @@ -6,11 +6,9 @@ Ready to see JWT Auth module in action. It demonstrates the JWT Auth Module usag ```sh $ cd $GOPATH -$ go get github.com/jeevatkm/jwtauth +$ go get github.com/revel/modules -$ ln -s $GOPATH/src/github.com/jeevatkm/jwtauth/example/jwtauth-example $GOPATH/src/github.com/jeevatkm/jwtauth-example - -$ revel run github.com/jeevatkm/jwtauth-example +$ revel run github.com/revel/modules/jwtauth/example/jwtauth-example # now application will be running http://localhost:9000 ``` diff --git a/jwtauth/example/jwtauth-example/app/controllers/app.go b/jwtauth/example/jwtauth-example/app/controllers/app.go index 7ad18b5..d6eabc9 100644 --- a/jwtauth/example/jwtauth-example/app/controllers/app.go +++ b/jwtauth/example/jwtauth-example/app/controllers/app.go @@ -3,8 +3,8 @@ package controllers import ( "fmt" - "github.com/jeevatkm/jwtauth-example/app/models" - "github.com/jeevatkm/jwtauth/app/jwt" + "github.com/revel/modules/jwtauth/app/jwt" + "github.com/revel/modules/jwtauth/example/jwtauth-example/app/models" "github.com/revel/revel" ) diff --git a/jwtauth/example/jwtauth-example/app/init.go b/jwtauth/example/jwtauth-example/app/init.go index 664351b..6f4b786 100644 --- a/jwtauth/example/jwtauth-example/app/init.go +++ b/jwtauth/example/jwtauth-example/app/init.go @@ -1,7 +1,7 @@ package app import ( - "github.com/jeevatkm/jwtauth/app/jwt" + "github.com/revel/modules/jwtauth/app/jwt" "github.com/revel/revel" ) diff --git a/jwtauth/example/jwtauth-example/conf/app.conf b/jwtauth/example/jwtauth-example/conf/app.conf index 4dd4782..eb1d0e0 100644 --- a/jwtauth/example/jwtauth-example/conf/app.conf +++ b/jwtauth/example/jwtauth-example/conf/app.conf @@ -95,7 +95,7 @@ i18n.default_language = en module.static=github.com/revel/modules/static # Enabling JWT Auth module -module.jwtauth = github.com/jeevatkm/jwtauth +module.jwtauth = github.com/revel/modules/jwtauth @@ -128,13 +128,13 @@ auth.jwt.sign.method = "RS512" # RSA key files # applicable to RS256, RS384, RS512 signing method and comment out others -auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_private.pem" -auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_public.pem" +auth.jwt.key.private = "${GOPATH}/src/github.com/revel/modules/jwtauth/example/jwtauth-example/conf/rsa_private.pem" +auth.jwt.key.public = "${GOPATH}/src/github.com/revel/modules/jwtauth/example/jwtauth-example/conf/rsa_public.pem" # Uncomment below two lines for ES256, ES384, ES512 signing method and comment out others # since we will be use ECDSA -#auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_private.pem" -#auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_public.pem" +#auth.jwt.key.private = "${GOPATH}/src/github.com/revel/modules/jwtauth/example/jwtauth-example/conf/ec_private.pem" +#auth.jwt.key.public = "${GOPATH}/src/github.com/revel/modules/jwtauth/example/jwtauth-example/conf/ec_public.pem" # HMAC signing Secret value # Uncomment below line for HS256, HS384, HS512 signing method and comment out others From c2da7ddcd3101be19456363bc3244d1f88c385fc Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Wed, 30 Sep 2015 14:23:33 -0700 Subject: [PATCH 17/18] Update of doc and readme --- jwtauth/README.md | 1 + jwtauth/app/jwt/jwt.go | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jwtauth/README.md b/jwtauth/README.md index f9222e2..550bb7f 100644 --- a/jwtauth/README.md +++ b/jwtauth/README.md @@ -37,6 +37,7 @@ auth.jwt.key.public = "/Users/jeeva/rsa_public.pem" #auth.jwt.key.hmac = "1A39B778C0CE40B1B32585CF846F61B1" # Valid regexp allowed for path +# Internally it will end up like this "^(/$|/token|/register|/(forgot|validate-reset|reset)-password)" auth.jwt.anonymous = "/, /token, /register, /(forgot|validate-reset|reset)-password, /freepass/.*" ``` diff --git a/jwtauth/app/jwt/jwt.go b/jwtauth/app/jwt/jwt.go index 6935ded..db04531 100644 --- a/jwtauth/app/jwt/jwt.go +++ b/jwtauth/app/jwt/jwt.go @@ -84,20 +84,20 @@ Method Init initializes JWT auth provider based on given config values from app. // RSA key files // applicable to RS256, RS384, RS512 signing method and comment out others - auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_private.pem" - auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/rsa_public.pem" + auth.jwt.key.private = "/Users/jeeva/rsa_private.pem" + auth.jwt.key.public = "/Users/jeeva/rsa_public.pem" // Uncomment below two lines for ES256, ES384, ES512 signing method and comment out others // since we will be use ECDSA - #auth.jwt.key.private = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_private.pem" - #auth.jwt.key.public = "${GOPATH}/src/github.com/jeevatkm/jwtauth-example/conf/ec_public.pem" + #auth.jwt.key.private = "/Users/jeeva/ec_private.pem" + #auth.jwt.key.public = "/Users/jeeva/ec_public.pem" // HMAC signing Secret value // Uncomment below line for HS256, HS384, HS512 signing method and comment out others #auth.jwt.key.hmac = "1A39B778C0CE40B1B32585CF846F61B1" // Valid regexp allowed for path - // Internal it will end up like this "^(/$|/token|/register|/(forgot|validate-reset|reset)-password)" + // Internally it will end up like this "^(/$|/token|/register|/(forgot|validate-reset|reset)-password)" auth.jwt.anonymous = "/, /token, /register, /(forgot|validate-reset|reset)-password" */ func Init(authHandler interface{}) { From 44c4703bbc64248e2705aac4872437a0d985feb9 Mon Sep 17 00:00:00 2001 From: Jeevanandam M Date: Sun, 10 Jan 2016 09:52:03 -0800 Subject: [PATCH 18/18] Added /validate is implemented for token. --- jwtauth/app/controllers/jwtauth.go | 9 +++++++++ jwtauth/conf/routes | 1 + 2 files changed, 10 insertions(+) diff --git a/jwtauth/app/controllers/jwtauth.go b/jwtauth/app/controllers/jwtauth.go index cf59a0f..51d8b10 100644 --- a/jwtauth/app/controllers/jwtauth.go +++ b/jwtauth/app/controllers/jwtauth.go @@ -74,6 +74,15 @@ func (c *JwtAuth) RefreshToken() revel.Result { }) } +func (c *JwtAuth) ValidateToken() revel.Result { + // When request reaches here, then it has valid auth token + // else request would have received 401 - Unauthorized response + return c.RenderJson(map[string]string{ + "id": "success", + "message": "Auth token is valid", + }) +} + func (c *JwtAuth) Logout() revel.Result { // Auth token will be added to blocklist for remaining token validitity period // Let's token is valid for another 10 minutes, then it reside 10 mintues in the blocklist diff --git a/jwtauth/conf/routes b/jwtauth/conf/routes index d18c2ba..9e3f469 100644 --- a/jwtauth/conf/routes +++ b/jwtauth/conf/routes @@ -1,4 +1,5 @@ # JWT Auth Routes POST /token JwtAuth.Token GET /refresh-token JwtAuth.RefreshToken +GET /validate JwtAuth.ValidateToken GET /logout JwtAuth.Logout