Credential based Authentication
Login each time when you need to operate something
Cookie Based Authentication
- The traditional approach of authentication
- Stateful. This means that an authentication record or session must be kept both server and client-side. The server needs to keep track of active sessions in a database (memory/local store), while on the front-end a cookie is created that holds a session identifier, thus the name cookie based authentication.
- User enters their login credentials
- Server verifies the credentials are correct and creates a session which is then stored in a database
- A cookie with the session ID is placed in the users browser
- On subsequent requests, the session ID is verified against the database and if valid the request processed
- Once a user logs out of the app, the session is destroyed both client and server side
Question: what’s the problem of this method?
Imagine when you need to replace your ID?
- Go to government, apply for a temporary ID.
- It may take them 1 months to send you a new ID.
- During this period, you may use your temporary ID for some purposes.
Token-Based Authentication
- More and more popular (Yelp API, etc.)
- Usually JSON Web Tokens (JWTs).
How it works
- User enters their login credentials (=username + password)
- Server verifies the credentials are correct and returns an encrypted and signed token with a private key.
- Text: (JSON = {username: “abcd”, uuid=”1.2.3.4”}, private key) => token
- Code: abccdeedddee
- Decode: JSON = {username: “abcd”, uuid=”1.2.3.4”}
- Symmetric vs Asymmetric encryption.
- This token is stored client-side, most commonly in local storage - but can be stored in session storage or a cookie as well
- Subsequent requests to the server include this token as an additional Authorization header or through one of the other methods mentioned above
- The server decodes the JWT and if the token is valid processes the request
- Once a user logs out, the token is destroyed client-side, no interaction with the server is necessary
Encryption using asymmetric keys.
Sign using asymmetric keys:
More reading (symmetric vs asymmetric encryption):
https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences
Advantages of Token-Based Authentication
Stateless, Scalable and Decoupled
- Stateless: The back-end does not need to keep a record of tokens.
- Self-contained, containing all the data required to check its validity..
- No DB look up is needed.
Store Data in the JWT
- With a cookie based approach, you simply store the session id in a cookie.
- JWT’s on the other hand allow you to store any type of metadata, as long as it’s valid JSON. (username in our project, for example)
Mobile Friendly
- Native mobile platforms and cookies do not mix well.
- In our project, the same Go backend will serve traffic to both iOS and browser (React JS)
Disadvantages of Token-Based Authentication
- Usually the size of token is larger than a session id.
Auth0 and Golang
Source of codes: https://auth0.com/blog/authentication-in-golang/
JWT
= JSON Web Toolkit
use jwt to protect post and search endpoints (reject if without auth token)
1 | package main |
Let’s explain these codes line by line
1 | r := mux.NewRouter() |
Create a new router on top of the existing http router as we need to check auth.
1 | var jwtMiddleware = jwtmiddleware.New(jwtmiddleware.Options{ |
Create a new JWT middleware with a Option that uses the key ‘mySigningKey’ such that we know this token is from our server. The signing method is the default HS256 algorithm such that data is encrypted.
1 | r.Handle("/post", jwtMiddleware.Handler(http.HandlerFunc(handlerPost))).Methods("POST") |
It means we use jwt middleware to manage these endpoints and if they don’t have valid token, we will reject them.
First handle the jwt, then chandle the normal http request
Question: if we reject it, what HttpResponse code we should return?
https://golang.org/src/net/http/status.go
Install new packages
1 | go get "github.com/auth0/go-jwt-middleware" |
Under the same folder, add a new file called user.go
1 | package main |
Implement checkUser method in user.go.
What does this method do?
We need to check whether a pair of username and password is stored in ES
.
1 | // checkUser checks whether user is valid |
Implement addUser method.
Student question
- After we send the term query, how do we know whether this user has existed?
- How to insert this user into ES?
1 | // Add a new user. Return true if successfully. |
Implement signupHandler method.
Student question: finish this method to support
- Decode a user from request (POST)
- Check whether username and password are empty, if any of them is empty, call
http.Error(w, "Empty password or username", http.StatusInternalServerError)
- Otherwise, call addUser, if true, return a message “User added successfully”
- If else, call http.Error(w, “Failed to add a new user”, http.StatusInternalServerError)
- Set header to be w.Header().Set(“Content-Type”, “text/plain”) w.Header().Set(“Access-Control-Allow-Origin”, “*”)
1 | // If signup is successful, a new session is created. |
Implement loginHandler
1 | // If login is successful, a new token is created. |
Let’s explain the codes
1 | decoder := json.NewDecoder(r.Body) |
Decode a user from request’s body1
if checkUser(u.Username, u.Password) {
Make sure user credential is correct.
1 | token := jwt.New(jwt.SigningMethodHS256) |
Create a new token object to store.
1 | claims := token.Claims.(jwt.MapClaims) |
Convert it into a map for lookup
1 | claims["username"] = u.Username |
Store username and expiration into it.
1 | tokenString, _ := token.SignedString(mySigningKey) |
Sign (Encrypt) and token such that only server knows it.
1 | w.Write([]byte(tokenString)) |
Write it into response
Update handlerPost in main.go to populate username
Question: why use the username in context? Why not ask user to send it as param?
Answer:
1 | func handlerPost(w http.ResponseWriter, r *http.Request) { |
Test
Local test
In the same folder where you have main.go and user.go
1 | go run *.go |
In windows, execute
go run main.go users.go
Open Postman and enter ‘http://localhost:8080/signup’ in the url, in the Body add a new json object as
1 | { |
Click Send and you should see a message like ‘User added successfully’.
Change the url to be ‘http://localhost:8080/login’, and in the body use the same json object.
It will return a token as response copy it
Post
change the url to ‘http://localhost:8080/post
’ and then in the header add a new key value with a key as ‘Authorization
’ and a value as ‘Bearer YOUR_TOKEN
’. In the body, still add related input params and an image file.
bearer is also fine
Click Send and make it works.
在这里elastic search默认只显示10条结果,需要在search handler那里修改一下结果数量。
Search
change the url to ‘http://localhost:8080/search?lat=37.5&lon=-120.5&range=200’ and the method to be GET. Then in the Headers, similarly, add a new Authorization key value pair if not there.
Click send and you should get the same results as last time.
You can also verify the signed content on:
https://jwt.io/
Remote test (Homework)
Deploy a new instance in GCloud and then test it again
REMEMBER: Install new packages on Google Cloud Shell!
Note: some new package may need to be installed on Google cloud shell:
1 | go get github.com/googleapis/gax-go |
Homework
- Try to send some random string and see what’s the response
- Why we don’t protect the two endpoints of ‘login’ and ‘signup’?
- How to protect credentials from man-in-the-middle attack?