Skip to content

Commit

Permalink
no longer using json for tokens, auth, refresh and csrf token names a…
Browse files Browse the repository at this point in the history
…re customizable
  • Loading branch information
adam-hanna committed May 9, 2017
1 parent f781edd commit a9bb53d
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 197 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ This longer-lived token will be used to update the auth tokens. These tokens hav
These refresh tokens contain an id which can be revoked by an authorized client.

### 3. CSRF Secret String
A CSRF secret string will be provided to each client and will be identical the CSRF secret in the auth and refresh tokens and will change each time an auth token is refreshed. These secrets will live in an "X-CSRF-Token" response header. These secrets will be sent along with the auth and refresh tokens on each api request.
A CSRF secret string will be provided to each client and will be identical the CSRF secret in the auth and refresh tokens and will change each time an auth token is refreshed. These secrets will live in an "X-CSRF-Token" response header, by default, but the header key can be set as an option. These secrets will be sent along with the auth and refresh tokens on each api request.

When request are made to protected endpoint, this CSRF secret needs to be sent to the server either as a hidden form value with a name of "X-CSRF-Token", in the request header with the key of "X-CSRF-Token", or in the "Authorization" request header with a value of "Basic " + token. This secret will be checked against the secret provided in the auth token in order to prevent CSRF attacks. It will be refreshed each time the auth token is refreshed from the refresh token.
When request are made to protected endpoint, this CSRF secret needs to be sent to the server either as a hidden form value with a name of "X-CSRF-Token", in the request header with the key of "X-CSRF-Token", or in the "Authorization" request header with a value of "Bearer " + token. This secret will be checked against the secret provided in the auth token in order to prevent CSRF attacks. It will be refreshed each time the auth token is refreshed from the refresh token.

## Cookies or Bearer Tokens?
This API is setup to either use cookies (default) or bearer tokens. To use bearer tokens, set the BearerTokens option equal to true in the config settings.

When using bearer tokens, you'll need to include the auth and refresh jwt's (along with your csrf secret) in each request. You can either include them as a form value or as data in the body of the request if Content-Type is application/json. The keys should be "Auth_Token" and "Refresh_Token", respectively. See the bearerTokens example for sample code of both.
When using bearer tokens, you'll need to include the auth and refresh jwt's (along with your csrf secret) in each request. Include them in the request headers. The keys can be defined in the auth options, but default to "X-Auth-Token" and "X-Refresh-Token", respectively. See the bearerTokens example for sample code of both.

Ideally, if using bearer tokens, they should be stored in a location that cannot be accessed with javascript. You want to be able to separate your csrf secret from your jwt's. If using web, I suggest using cookies. If using mobile, store these in a secure manner!

Expand All @@ -128,9 +128,12 @@ type Options struct {
PublicKeyLocation string // only for RSA and ECDSA signing methods
HMACKey []byte // only for HMAC-SHA signing method
VerifyOnlyServer bool // false = server can verify and issue tokens (default); true = server can only verify tokens
BearerTokens bool // false = server uses cookies to transport jwts (default); true = server uses bearer tokens
BearerTokens bool // false = server uses cookies to transport jwts (default); true = server uses request headers
RefreshTokenValidTime time.Duration
AuthTokenValidTime time.Duration
AuthTokenName string // defaults to "AuthToken" for cookies and "X-Auth-Token" for bearer tokens
RefreshTokenName string // defaults to "RefreshToken" for cookies and "X-Refresh-Token" for bearer tokens
CSRFTokenName string // defaults to "X-CSRF-Token"
Debug bool // true = more logs are shown
IsDevEnv bool // true = in development mode; this sets http cookies (if used) to insecure; false = production mode; this sets http cookies (if used) to secure
}
Expand Down
32 changes: 14 additions & 18 deletions examples/bearerTokens/templates/templateFiles/login.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
console.log("Success!", data, textStatus, request);

window.localStorage.setItem("X-CSRF-Token", request.getResponseHeader("X-CSRF-Token"));
window.localStorage.setItem("Auth_Token", request.getResponseHeader("Auth_Token"));
window.localStorage.setItem("Refresh_Token", request.getResponseHeader("Refresh_Token"));
window.localStorage.setItem("X-Auth-Token", request.getResponseHeader("X-Auth-Token"));
window.localStorage.setItem("X-Refresh-Token", request.getResponseHeader("X-Refresh-Token"));

$("#secret").html(data.secret);
},
Expand All @@ -78,27 +78,25 @@
}

$("#refreshSecret").on("click", function() {
var data = {
"Auth_Token": window.localStorage.getItem("Auth_Token"),
"Refresh_Token": window.localStorage.getItem("Refresh_Token")
};
var headers = {
"Authorization": "Basic " + window.localStorage.getItem("X-CSRF-Token")
"Authorization": "Bearer " + window.localStorage.getItem("X-CSRF-Token"),
"X-Auth-Token": window.localStorage.getItem("X-Auth-Token"),
"X-Refresh-Token": window.localStorage.getItem("X-Refresh-Token")
};

$.ajax({
'type': 'POST',
'url': '/refreshSecret',
'headers': headers,
'data': JSON.stringify(data),
'data': JSON.stringify({}),
'dataType': 'json',
'contentType': 'application/json',
success: function(data, textStatus, request) {
console.log("Success!", data, textStatus, request);

window.localStorage.setItem("X-CSRF-Token", request.getResponseHeader("X-CSRF-Token"));
window.localStorage.setItem("Auth_Token", request.getResponseHeader("Auth_Token"));
window.localStorage.setItem("Refresh_Token", request.getResponseHeader("Refresh_Token"));
window.localStorage.setItem("X-Auth-Token", request.getResponseHeader("X-Auth-Token"));
window.localStorage.setItem("X-Refresh-Token", request.getResponseHeader("X-Refresh-Token"));

$("#secret").html(data.secret);
},
Expand All @@ -109,25 +107,23 @@
});

$("#logout").on("click", function() {
var data = {
"Auth_Token": window.localStorage.getItem("Auth_Token"),
"Refresh_Token": window.localStorage.getItem("Refresh_Token")
};
var headers = {
"Authorization": "Basic " + window.localStorage.getItem("X-CSRF-Token")
"Authorization": "Bearer " + window.localStorage.getItem("X-CSRF-Token"),
"X-Auth-Token": window.localStorage.getItem("X-Auth-Token"),
"X-Refresh-Token": window.localStorage.getItem("X-Refresh-Token")
};

$.ajax({
'type': 'POST',
'url': '/logout',
'headers': headers,
'data': data,
'data': {},
success: function(data, textStatus, request) {
console.log("Success!", data, textStatus, request);

window.localStorage.setItem("X-CSRF-Token", "");
window.localStorage.setItem("Auth_Token", "");
window.localStorage.setItem("Refresh_Token", "");
window.localStorage.setItem("X-Auth-Token", "");
window.localStorage.setItem("X-Refresh-Token", "");

$("#secret").html("Please login!");
},
Expand Down
3 changes: 1 addition & 2 deletions examples/separateAuthServer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ var myUnauthorizedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http
})

var restrictedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csrfSecret := w.Header().Get("X-CSRF-Token")
claims, err := restrictedRoute.GrabTokenClaims(r)
log.Println(claims)

Expand All @@ -153,7 +152,7 @@ var restrictedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Req
return
}

templates.RenderTemplate(w, "restricted", &templates.RestrictedPage{csrfSecret, claims.CustomClaims["Role"].(string)})
templates.RenderTemplate(w, "restricted", &templates.RestrictedPage{claims.Csrf, claims.CustomClaims["Role"].(string)})
})

var issueClaimsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
26 changes: 13 additions & 13 deletions jwt/auth-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ func (a *Auth) extractTokenStringsFromReq(r *http.Request) (string, string, *jwt
if a.options.BearerTokens {
// tokens are not in cookies
// Note: we don't check for errors here, because we will check if the token is valid, later
return r.Header.Get("X-Auth-Token"), r.Header.Get("X-Refresh-Token"), nil
return r.Header.Get(a.options.AuthTokenName), r.Header.Get(a.options.RefreshTokenName), nil
}

AuthCookie, authErr := r.Cookie("AuthToken")
AuthCookie, authErr := r.Cookie(a.options.AuthTokenName)
if authErr == http.ErrNoCookie {
a.myLog("Unauthorized attempt! No auth cookie")
return "", "", newJwtError(errors.New("No auth cookie"), 401)
Expand All @@ -27,7 +27,7 @@ func (a *Auth) extractTokenStringsFromReq(r *http.Request) (string, string, *jwt
return "", "", newJwtError(errors.New("Internal Server Error"), 500)
}

RefreshCookie, refreshErr := r.Cookie("RefreshToken")
RefreshCookie, refreshErr := r.Cookie(a.options.RefreshTokenName)
if refreshErr == http.ErrNoCookie {
a.myLog("Unauthorized attempt! No refresh cookie")
return "", "", newJwtError(errors.New("No refresh cookie"), 401)
Expand All @@ -39,20 +39,20 @@ func (a *Auth) extractTokenStringsFromReq(r *http.Request) (string, string, *jwt
return AuthCookie.Value, RefreshCookie.Value, nil
}

func extractCsrfStringFromReq(r *http.Request) (string, *jwtError) {
csrfString := r.FormValue("X-CSRF-Token")
func (a *Auth) extractCsrfStringFromReq(r *http.Request) (string, *jwtError) {
csrfString := r.FormValue(a.options.CSRFTokenName)

if csrfString != "" {
return csrfString, nil
}

csrfString = r.Header.Get("X-CSRF-Token")
csrfString = r.Header.Get(a.options.CSRFTokenName)
if csrfString != "" {
return csrfString, nil
}

auth := r.Header.Get("Authorization")
csrfString = strings.Replace(auth, "Basic", "", 1)
csrfString = strings.Replace(auth, "Bearer", "", 1)
csrfString = strings.Replace(csrfString, " ", "", -1)
if csrfString == "" {
return csrfString, newJwtError(errors.New("No CSRF string"), 401)
Expand All @@ -73,13 +73,13 @@ func (a *Auth) setCredentialsOnResponseWriter(w http.ResponseWriter, c *credenti

if a.options.BearerTokens {
// tokens are not in cookies
setHeader(w, "X-Auth-Token", authTokenString)
setHeader(w, "X-Refresh-Token", refreshTokenString)
setHeader(w, a.options.AuthTokenName, authTokenString)
setHeader(w, a.options.RefreshTokenName, refreshTokenString)
} else {
// tokens are in cookies
// note: don't use an "Expires" in auth cookies bc browsers won't send expired cookies?
authCookie := http.Cookie{
Name: "AuthToken",
Name: a.options.AuthTokenName,
Value: authTokenString,
// Expires: time.Now().Add(a.options.AuthTokenValidTime),
HttpOnly: true,
Expand All @@ -88,7 +88,7 @@ func (a *Auth) setCredentialsOnResponseWriter(w http.ResponseWriter, c *credenti
http.SetCookie(w, &authCookie)

refreshCookie := http.Cookie{
Name: "RefreshToken",
Name: a.options.RefreshTokenName,
Value: refreshTokenString,
Expires: time.Now().Add(a.options.RefreshTokenValidTime),
HttpOnly: true,
Expand All @@ -108,7 +108,7 @@ func (a *Auth) setCredentialsOnResponseWriter(w http.ResponseWriter, c *credenti
return newJwtError(errors.New("Cannot read token claims"), 500)
}

w.Header().Set("X-CSRF-Token", c.CsrfString)
w.Header().Set(a.options.CSRFTokenName, c.CsrfString)
// note @adam-hanna: this may not be correct when using a sep auth server?
// bc it checks the request?
w.Header().Set("Auth-Expiry", strconv.FormatInt(authTokenClaims.StandardClaims.ExpiresAt, 10))
Expand All @@ -123,7 +123,7 @@ func (a *Auth) buildCredentialsFromRequest(r *http.Request, c *credentials) *jwt
return newJwtError(err, 500)
}

csrfString, err := extractCsrfStringFromReq(r)
csrfString, err := a.extractCsrfStringFromReq(r)
if err != nil {
return newJwtError(err, 500)
}
Expand Down
Loading

0 comments on commit a9bb53d

Please sign in to comment.