forked from advanced-security/demo-golang
-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
checking security in 3rd party library - Jwt lib #3
Open
bkrockx
wants to merge
2
commits into
main
Choose a base branch
from
jwt-go-example-test
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/dgrijalva/jwt-go" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
var jwtKey = []byte("my_secret_key") | ||
|
||
var users = map[string]string{ | ||
"user1": "password1", | ||
"user2": "password2", | ||
} | ||
|
||
// Create a struct that models the structure of a user, both in the request body, and in the DB | ||
type Credentials struct { | ||
Password string `json:"password"` | ||
Username string `json:"username"` | ||
} | ||
|
||
// | ||
type Claims struct { | ||
Username string `json:"username"` | ||
jwt.StandardClaims | ||
} | ||
|
||
func Signin(w http.ResponseWriter, r *http.Request) { | ||
var creds Credentials | ||
// Get the JSON body and decode into credentials | ||
err := json.NewDecoder(r.Body).Decode(&creds) | ||
if err != nil { | ||
// If the structure of the body is wrong, return an HTTP error | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Get the expected password from our in memory map | ||
expectedPassword, ok := users[creds.Username] | ||
|
||
// If a password exists for the given user | ||
// AND, if it is the same as the password we received, the we can move ahead | ||
// if NOT, then we return an "Unauthorized" status | ||
if !ok || expectedPassword != creds.Password { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
// Declare the expiration time of the token | ||
// here, we have kept it as 5 minutes | ||
expirationTime := time.Now().Add(5 * time.Minute) | ||
// Create the JWT claims, which includes the username and expiry time | ||
claims := &Claims{ | ||
Username: creds.Username, | ||
StandardClaims: jwt.StandardClaims{ | ||
// In JWT, the expiry time is expressed as unix milliseconds | ||
ExpiresAt: expirationTime.Unix(), | ||
}, | ||
} | ||
|
||
// Declare the token with the algorithm used for signing, and the claims | ||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | ||
// Create the JWT string | ||
tokenString, err := token.SignedString(jwtKey) | ||
if err != nil { | ||
// If there is an error in creating the JWT return an internal server error | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Finally, we set the client cookie for "token" as the JWT we just generated | ||
// we also set an expiry time which is the same as the token itself | ||
http.SetCookie(w, &http.Cookie{ | ||
Name: "token", | ||
Value: tokenString, | ||
Expires: expirationTime, | ||
}) | ||
} | ||
|
||
func Welcome(w http.ResponseWriter, r *http.Request) { | ||
// We can obtain the session token from the requests cookies, which come with every request | ||
c, err := r.Cookie("token") | ||
if err != nil { | ||
if err == http.ErrNoCookie { | ||
// If the cookie is not set, return an unauthorized status | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
// For any other type of error, return a bad request status | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Get the JWT string from the cookie | ||
tknStr := c.Value | ||
|
||
// Initialize a new instance of `Claims` | ||
claims := &Claims{} | ||
|
||
// Parse the JWT string and store the result in `claims`. | ||
// Note that we are passing the key in this method as well. This method will return an error | ||
// if the token is invalid (if it has expired according to the expiry time we set on sign in), | ||
// or if the signature does not match | ||
tkn, err := jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) { | ||
return jwtKey, nil | ||
}) | ||
if err != nil { | ||
if err == jwt.ErrSignatureInvalid { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
if !tkn.Valid { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
// Finally, return the welcome message to the user, along with their | ||
// username given in the token | ||
w.Write([]byte(fmt.Sprintf("Welcome %s!", claims.Username))) | ||
} | ||
|
||
func Refresh(w http.ResponseWriter, r *http.Request) { | ||
// (BEGIN) The code uptil this point is the same as the first part of the `Welcome` route | ||
c, err := r.Cookie("token") | ||
if err != nil { | ||
if err == http.ErrNoCookie { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
tknStr := c.Value | ||
claims := &Claims{} | ||
tkn, err := jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) { | ||
return jwtKey, nil | ||
}) | ||
if !tkn.Valid { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
if err != nil { | ||
if err == jwt.ErrSignatureInvalid { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
// (END) The code uptil this point is the same as the first part of the `Welcome` route | ||
|
||
// We ensure that a new token is not issued until enough time has elapsed | ||
// In this case, a new token will only be issued if the old token is within | ||
// 30 seconds of expiry. Otherwise, return a bad request status | ||
if time.Unix(claims.ExpiresAt, 0).Sub(time.Now()) > 30*time.Second { | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Now, create a new token for the current use, with a renewed expiration time | ||
expirationTime := time.Now().Add(5 * time.Minute) | ||
claims.ExpiresAt = expirationTime.Unix() | ||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | ||
tokenString, err := token.SignedString(jwtKey) | ||
if err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Set the new token as the users `session_token` cookie | ||
http.SetCookie(w, &http.Cookie{ | ||
Name: "session_token", | ||
Value: tokenString, | ||
Expires: expirationTime, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
POST http://localhost:8000/signin | ||
Content-Type: application/json | ||
|
||
{ | ||
"username": "user1", | ||
"password": "password1" | ||
} | ||
|
||
### | ||
|
||
GET http://localhost:8000/welcome | ||
|
||
### | ||
|
||
POST http://localhost:8000/refresh |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check failure
Code scanning / CodeQL
Database query built from user-controlled sources