I am creating jwt tokens using jwt-go library. Later wrote a script to load test. I have noticed when I send the many concurrent request getting same token. To check more about this I created token inside for loop and result is same.
The library that I use is https://github.com/dgrijalva/jwt-go, go version is 1.12.9.
expirationTime := time.Now().Add(time.Duration(60) * time.Minute)
for i := 1; i < 5; i++ {
claims := &jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
Issuer:"telescope",
}
_token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
var jwtKey = []byte("secret_key")
auth_token, _ := _token.SignedString(jwtKey)
fmt.Println(auth_token)
}
A JWT contains three parts: a mostly-fixed header, a set of claims, and a signature. RFC 7519 has the actual details. If the header is fixed and the claims are identical between two tokens, then the signature will be identical too, and you can easily get duplicated tokens. The two timestamp claims "iat" and "exp" are only at a second granularity, so if you issue multiple tokens with your code during the same second you will get identical results (even if you move the expirationTime calculation inside the loop).
The jwt-go library exports the StandardClaims listed in RFC 7519 ยง4.1 as a structure, which is what you're using in your code. Digging through the library code, there's nothing especially subtle here: StandardClaims uses ordinary "encoding/json" annotations, and then when a token is written out, the claims are JSON encoded and then base64-encoded. So given a fixed input, you'll get a fixed output.
If you want every token to be "different" in some way, the standard "jti" claim is a place to provide a unique ID. This isn't part of the StandardClaims, so you need to create your own custom claim type that includes it.
type UniqueClaims struct {
jwt.StandardClaims
TokenId string `json:"jti,omitempty"`
}
Then when you create the claims structure, you need to generate a unique TokenId yourself.
import (
"crypto/rand"
"encoding/base64"
)
bits := make([]byte, 12)
_, err := rand.Read(bits)
if err != nil {
panic(err)
}
claims := UniqueClaims{
StandardClaims: jwt.StandardClaims{...},
TokenId: base64.StdEncoding.EncodeToString(bits),
}
https://play.golang.org/p/zDnkamwsCi- has a complete example; every time you run it you will get a different token, even if you run it multiple times in the same second. You can base64 decode the middle part of the token by hand to see the claims, or use a tool like the https://jwt.io/ debugger to decode it.
I changed your code:
Moved calculation of expirationTime in the loop
Added 1 sec delay on each step of loop
for i := 1; i < 5; i++ {
expirationTime := time.Now().Add(time.Duration(60) * time.Minute)
claims := &jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
Issuer: "telescope",
}
_token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
var jwtKey = []byte("secret_key")
auth_token, _ := _token.SignedString(jwtKey)
fmt.Println(auth_token)
time.Sleep(time.Duration(1) * time.Second)
}
In this case we get different tokens:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjcyNDcwNDgsImlzcyI6InRlbGVzY29wZSJ9.G7wV-zsCYjysLEdgYAq_92JGDPsgqqOz9lZxdh5gcX8
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjcyNDcwNDksImlzcyI6InRlbGVzY29wZSJ9.yPNV20EN3XJbGiHhe-wGTdiluJyVHXj3nIqEsfwDZ0Q
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjcyNDcwNTAsImlzcyI6InRlbGVzY29wZSJ9.W3xFXEiVwh8xK47dZinpXFpKuvUl1LFUAiaLZZzZ2L0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjcyNDcwNTEsImlzcyI6InRlbGVzY29wZSJ9.wYUbzdXm_VQGdFH9RynAVVouW9h6KI1tHRFJ0Y322i4
Sorry, I am not big expert in JWT and I hope somebody who is explain us this behavior from RFC point of view.
I want to get different tokens. eg : same person login in to system using different browser. so I want to keep many tokens.
It is the same user and we can get him the same token. If we want to give it another one we need to revoke previous one or the client must refresh it.
Related
I am going through Gmail API docs(https://developers.google.com/gmail/api), I am able to read all the user emails which are present in the inbox.
eg snippet (reading complete list of emails):
for {
req := svc.Users.Messages.List("me")
r, _ := req.Do()
for _, m := range r.Messages {
msg, _ := svc.Users.Messages.Get("me", m.Id).Do()
date := ""
for _, h := range msg.Payload.Headers {
if h.Name == "Date" {
date = h.Value
break
}
}
msgs = append(msgs, message{
...
})
}
}
Now, when new emails come I want to read them as well (either immediately or after some time). I can write a scheduled job for that purpose, But I am not sure if I can fetch email after a particular timestamp or after an email identifier. I don't want to read a whole bunch of emails, again and again, to figure out the new emails, in this way, there is a lot of unnecessary computation being involved. Is there any way I can make this task easier?
Looking at the docs, it looks like it supports a query parameter, q.
The query parameter supports the same options as available in the Gmail search bar:
Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser#example.com rfc822msgid:somemsgid#example.com is:unread". Parameter cannot be used when accessing the api using the gmail.metadata scope.
This means you can do something like "after:YYYY/MM/DD" or with a timestamp "after:1616819452".
req := svc.Users.Messages.List("me").Q("after:2021/01/01")
See the full usage here https://pkg.go.dev/google.golang.org/api/gmail/v1#UsersMessagesListCall.Q
I have the following requirement: return errors from a REST API in the following format:
Error format
422
{
"name-of-field": [
"can't be blank",
"is too silly"
]
}
My code looks like this:
var PostFeedback = func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
surveyId := params["id"]
feedback := &models.Feedback{}
err := json.NewDecoder(r.Body).Decode(feedback)
if err != nil {
jsonError := fmt.Sprintf(`{
"%s": [
"%s"
]
}`, "errors", err)
log.Printf("invalid input format, %v", jsonError)
resp := map[string]interface{}{"error": jsonError}
u.Respond(w, resp)
return
}
Questions:
How do I get the names of the offending fields?
How do I satisfy the requirement best?
The encoding/json package doesn't provide validation for "blank", nor "silly" values. It will return an error only if the data in the body is not a valid json, or if the field types in the json do not, according to the package's spec, match the field types of the structure into which you're trying to decode that json.
The 1st type of error would be the json.SyntaxError, if you get this it is not always possible to satisfy your requirements since there may be no actual fields which you could use in your response, or if there are json fields, they, and their values, may be perfectly valid json, but the cause of the error may lie elsewhere (see example).
In cases where the data holds actual json fields but it has, for example, non-json values you could use the Offset field of the SyntaxError type to find the closest preceding field in the data stream. Using strings.LastIndex you can implement a naive solution to look backwards for the field.
data := []byte(`{"foobar": i'm not json}`)
err := json.Unmarshal(data, &T{})
se, ok := err.(*json.SyntaxError)
if !ok {
panic(err)
}
field := string(data[:se.Offset])
if i := strings.LastIndex(field, `":`); i >= 0 {
field = field[:i]
if j := strings.LastIndex(field, `"`); j >= 0 {
field = field[j+1:]
}
}
fmt.Println(field) // outputs foobar
Playground link
NOTE: As you can see, for you to be able to look for the field, you need to have access to the data, but when you're using json.NewDecoder and passing it the request's body directly, without first storing its contents somewhere, you'll loose access to that data once the decoder's Decode method is done. This is because the body is a stream of bytes wrapped in a io.ReadCloser that does not support "rewinding", i.e. you cannot re-read bytes that the decoder already read. To avoid this you can use ioutil.ReadAll to read the full contents of the body and then json.Unmarshal to do the decoding.
The 2nd type of error would be the json.UnmarshalTypeError. If you look at the documentation of the error type and its fields you'll know that all you need to do is to type assert the returned value and you're done. Example
Validation against "blank" and "silly" values would be done after the json has been successfully decoded into your structure. How you do that is up to you. For example you could use a 3rd party package that's designed for validating structs, or you can implement an in-house solution yourself, etc. I actually don't have an opinion on which one of them is the "best" so I can't help you with that.
What I can say is that the most basic approach would be to simply look at each field of the structure and check if its value is valid or not according to the requirements for that field.
I have this function:
func GetSigningKey() *rsa.PublicKey {
set, _ := jwk.ParseString(GetWellKnown())
publicKey, _ := set.Keys[0].Materialize()
return publicKey.(*rsa.PublicKey)
}
.Materialize() returns interface{}, so I use this function to cast it to (what I think is) the expected type.
I can then use that token with:
publicKey := GetSigningKey()
token, _ := jwt.Parse(tokenString, func(*jwt.Token) (interface{}, error) {
return publicKey, nil
})
fmt.Println(token.Valid)
A few questions:
The purpose of the GetSigningKey function is to obtain the signing key from a well known file and cast the resulting key to the correct type.
1) This requires an http request which lives within GetWellKnown(). In that function, I do the http request and cache the response in a variable so following requests will pull from that variable. Is this a good practice? (follow up, should I do the same thing to the parsed public key in GetSigningKey())
2) Am I right in assuming that this is the best way to cast the interface{} to the correct type?
3) if I try fmt.Println(GetSigningKey()) I get a long, unrecognisable sequence of numbers
&{2568409364095596940117712027335... %!s(int=65537)}635 <nil>
What exactly am I seeing here?
There's no error checking in your code. Especially if the JWKS is empty or contains something that's not an RSA key, your program will just crash, and that tends to not be the behavior encouraged in Go. I'd add some checking around the whole thing; for instance,
func GetSigningKey() *rsa.PublicKey, error {
...
if rsaKey, ok := publicKey.(*rsa.PublicKey); ok {
return rsaKey, nil
}
return nil, errors.New("not an RSA key")
}
Remember that other signing methods exist and in principle you can get back an ECDSA key or (in theory) a raw key for symmetric signing, so checking here is valuable.
The other high-level thing you should try is to take the raw JSON Web token and decode it; https://jwt.io/ has a debugger I use. If the header contains a claim named "kid", that is a key ID, and you should be able to pass that key ID as an input to the JWKS library. That should be more robust than blindly using the first key in the JWKS.
Online JWKS documents can change, but seem to generally do so infrequently (on a scale of months). (If they do change, it's likely that a JWKS will include both old and new keys.) Caching them in-process is extremely reasonable. If you see a "kid" you don't recognize that can be a signal to try refetching the document.
Your fmt.Printf output looks like it's probably the raw RSA public key. The public key itself consists of two numbers, one being the product of two large prime numbers and the second frequently being exactly 65537. There is some more discussion of this in the Wikipedia description of RSA.
In trying to set up two different web.Request states for use in some test cases, one without any headers and one with, I run into issue:
Setup
I create fakeRequest,fakeRequestNoHeaders thus:
// create fake request
fakeRequest := new(web.Request)
fakeRequest.Request = httptest.NewRequest("GET",
fakeServer.URL,
nil)
fakeRequestNoHeaders := new(web.Request)
fakeRequestNoHeaders.Request = fakeRequest.Request
// give fakeRequest some headers
fakeRequest.Header.Add("Authorization", "Bearer ksjaf;oipyu7")
fakeRequest.Header.Add("Scope", "test")
Sanity Test
I expect, of course, that fakeRequest.Header != fakeRequestNoHeaders.Header.
I write that test:
t.Run("HeadersSanityTest", func(t *testing.T) {
assert.NotEqualf(t,
fakeRequest.Header,
fakeRequestNoHeaders.Header,
"fakeRequest,fakeRequestNoHeaders share the same header state")
Result of test
It fails.
Why is this and how can I achieve what I'm trying?
UPDATE: I found the culprit:
the underlying http.Request, returned by httptest.NewRequest, is actually a pointer. Header simply belongs to that Request. The problem now reduces down to "How to deep-copy that Request."
The issue was, indeed, not with the Header field, but instead the Request field, which was a pointer. (Oh no! I accidentally shallow-copied)
The Solution
I recalled, in one of my earlier tests, a method that I wrote specifically to get around this:
func makeBasicRequest() *web.Request {
baseReq := httptest.NewRequest(http.MethodPost,
"[some url here]",
nil)
req := new(web.Request)
req.Request = baseReq
return req
}
I basically just brought it into this test, and used it, hitting it once per fake request that I needed.
As an exercise, I'm trying to implement a mock SMTP server with CRAM-MD5 authentication in Go (without following RFC 2195, since it looks like it doesn't matter to the client what format the pre-hashed challenge is in; I also assume there is only one user "bob" with password "pass"). But I can't seem to get it right as the hash in response is always different from what I have on the server. I send the email using Go as such (running it as a separate package):
{...}
smtp.SendMail("localhost:25", smtp.CRAMMD5Auth("bob", "pass"),
"bob#localhost", []string{"alice#localhost"}, []byte("Hey Alice!\n"))
{...}
Here's what I do when I get the authentication acknowledgement from the client:
{...}
case strings.Contains(ms, "AUTH CRAM-MD5"):
rndbts = make([]byte, 16) // Declared at package level
b64b := make([]byte, base64.StdEncoding.EncodedLen(16))
rand.Read(rndbts)
base64.StdEncoding.Encode(b64b, rndbts)
_, err = conn.Write([]byte(fmt.Sprintf("334 %x\n", b64b)))
{...}
And this is what I do with the client's response:
{...}
{
ms = strings.TrimRight(ms, "\r\n") // The response to the challenge
ds, _ := base64.StdEncoding.DecodeString(ms)
s := strings.Split(string(ds), " ")
login := s[0] // I can get the login from the response.
h := hmac.New(md5.New, []byte("pass"))
h.Write(rndbts)
c := make([]byte, 0, ourHash.Size()) // From smtp/auth.go, not sure why we need this.
validPass := hmac.Equal(h.Sum(c), []byte(s[1]))
{...}
}
{...}
And the validPass is never true. I omitted error handling from the excerpts for brevity, but they're there in the actual code (though they're always nil). Why are the hashes different? I have looked at the source code for net/smtp, and it seems to me that I'm going in the right direction, but not quite.
I hope this helps! Runnable version is at https://play.golang.org/p/-8shx_IcLV. Also note that you'll need to fix your %x (should be %s) so you're sending the right challenge down to the client. Right now I think you're trying to hex-encode your base64 string.
Once you've fixed that, I believe this code should help you to construct the right response string on the server and compare it to what the client sent.
// Example values taken from http://susam.in/blog/auth-cram-md5/
challenge := []byte("<17893.1320679123#tesseract.susam.in>")
username := []byte("alice")
password := []byte("wonderland")
clientResponse := []byte("YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=")
// hash the challenge with the user's password
h := hmac.New(md5.New, password)
h.Write(challenge)
hash := h.Sum(nil)
// encode the result in lowercase hexadecimal
hexEncoded := hex.EncodeToString(hash)
// prepend the username and a space
toEncode := []byte(string(username) + " " + hexEncoded)
// base64-encode the whole thing
b64Result := make([]byte, base64.StdEncoding.EncodedLen(len(toEncode)))
base64.StdEncoding.Encode(b64Result, toEncode)
// check that this is equal to what the client sent
if hmac.Equal(b64Result, clientResponse) {
fmt.Println("Matches!")
}