[Go] Try HTTP Authentication 1
Tags: go
Published: 2022-09-24 14:48:10

## Intro
I will try implementing HTTP Authentication with 'Basic' HTTP Authentication.

After authentication, I will set a session key as a cookie value.

Note:
This sample was created simply to see how "Basic" authentication works. 
In practice, security measures must be applied.

## 'Basic' HTTP Authentication
'Basic' HTTP Authentication uses a pair of ID and password for authentication.
And it's based on "Challenge-response authentication".

1. [Client] Access the protected resources
2. [Server] Because the client does not have the credentials、The server returns a 401 status code and "WWW-Authenticate".
3. [Client] Send valid ID and password
4. [Server] The authentication is succeeded and the server will return the protected resources

### 2. Returning a 401 status code and "WWW-Authenticate"
At first I will see how web browsers work when the server returns the 401 statu code.

#### main.go
```go
package main

import (
	"log"
	"net/http"
)

func main() {
    // It only returns 401 status code now. 
	http.HandleFunc("/auth/", func(w http.ResponseWriter, r *http.Request) {
        // TODO: Validate the user authentication info

        // At least one "WWW-Authenticate" is required.
        // Otherwise, web browsers just show the default 401 error page.
        w.Header().Set("WWW-Authenticate", "Basic realm=\"localhost\"")
        w.WriteHeader(401)
    })
	log.Fatal(http.ListenAndServe("localhost:8083", nil))
}
```

#### Result

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znyvgjj7hgnh4o7m2x3t.png)
### 4. Get the client user-pass(ID and password)
After the client input its ID and password, the server can get them from HTTP request header.

The web brower sets them into HTTP request header as "user-pass".
The "user-pass" is generated in the following format and encoded in Base64. 

```
{ID}:{Password}
```

The web browser registers it with the "Authorization" key along with the Scheme.

```
Basic {user-pass}
```

The server can split them into the Scheme(Basic), the ID, and the password.

#### main.go
```go
...
import (
	"encoding/base64"
	"log"
	"net/http"
	"strings"
)
...
func signin(r *http.Request) {
	// Get user-pass
	authText := r.Header.Get("Authorization")
	splitted := strings.Split(authText, " ")
	if len(splitted) >= 2 {
		log.Printf("Authorization: %s UserPass: %s", splitted[0], splitted[1])
		decoded, err := base64.StdEncoding.DecodeString(splitted[1])
		if err == nil {
			userPass := string(decoded)
			splitTarget := strings.Index(userPass, ":")
			if splitTarget < 0 {
                log.Println("Invalid")
				return
			}
			userId := userPass[0:splitTarget]
			pass := userPass[splitTarget+1:]
			log.Printf("USER ID:%s Password:%s", userId, pass)
		} else {
			log.Println(err.Error())
		}
	} else {
        log.Println("Invalid")
    }    
}
```

#### Results
```
Authorization: Basic UserPass: ZmZmZ2hqazpkZmdoamts
USER ID:fffghjk Password:dfghjkl
```

## Saving the authentication info
After authentication, how should I keep my credentials?

For example, ASP.NET Core Identity registers authentication information in cookies by default.

* [【.NET 5】【ASP.NET Core Identity】SignIn with custom user](https://dev.to/masanori_msl/net-5-asp-net-core-identity-signin-with-custom-user-56fe)

I will try registering in cookies in this time.

According to my ASP.NET Core application run, the cookie set after authentication was as follows.

```
Cookie: .AspNetCore.Identity.Application=CfDJ8PAT8IN6xqxGqh9UWuW_Iif6kvwPRUoA5MEVumnp6qYnZySdcFGwZhWqaznPiKtjbsiyfYLzPmyKHs_wSDYZqSp61aiv-wzRYg5zuisGsOyVpSbKrCUkxUMSTQIrXFdNeVMdb7QDQYJahbbdgjSmZc5kOAuqK8WQh1fCQ0GxbE4XrvwEDClLm-SSRyOC72Ne76uBnGRHPzuGOFXUMklWeRcRWLPEd3ZOwMkwChKkSMA7xJjHAPtCnx0LLkZJ30YmfsbRPJ6qBWiSCoq-ChVzWZLKog0mR4SLBz7K_dEbUfdfMeAi52sXiJkk8rHSaHxxDymF4KJu8-Qg6xZ4e--02xCc-dFedut7Q2NXQPXD7PXY_wV-VB0x60KRQYNc_UeZDvuZWPZi-vgICqgraMFHoynKLEQgRbmTYU6SVRb_MX920fccmtySo8y5-xZ6P_Drmko1hqNyyZKamMKxqHxXXXX
```

Because I couldn't find a standard specification for storing authentication information in cookies, I figured I had to decide for myself what the key and value of the cookie would be.


#### Set cookies
```go
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 1)
cookie := http.Cookie{Name: "WebAuthSample", Value: uuid.New().String(), Expires: expiration}
http.SetCookie(w, &cookie)
```

#### Get cookies
```go
func getAuthenticationInfo(r *http.Request) string {
	for _, c := range r.Cookies() {
		if c.Name != "WebAuthSample" {
			continue
		}
        // If the client has a valid cookie, it can retrieve the value.
		return c.String()
	}
	return ""
}
```

### TODO
The server-side application should maintain the ID and cookie values of clients who have completed the authentication process, and check the authentication status when the client accesses protected resources.

## Resources
* [RFC9110 - HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110.html)
* [RFC7617 - The 'Basic' HTTP Authentication Scheme](https://www.rfc-editor.org/rfc/rfc7617.html)
* [RFC6525 - HTTP State Management Mechanism](https://www.rfc-editor.org/rfc/rfc6265.html)
* [HTTP authentication - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)
* [Using HTTP cookies - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
* [base64 - encoding/base64 - Go Packages](https://pkg.go.dev/encoding/base64)
* [uuid - github.com/google/uuid - Go Packages](https://pkg.go.dev/github.com/google/uuid)