Skip to content

Commit

Permalink
ability to set a default bearer token (#556)
Browse files Browse the repository at this point in the history
* ability to set a default bearer token

* linting fixes

* moving docs to service section

---------

Co-authored-by: Gabe <[email protected]>
  • Loading branch information
michaelneale and decentralgabe authored Aug 1, 2023
1 parent ab19193 commit 02d6f8f
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 9 deletions.
3 changes: 3 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ resource for developers and users of the SSI Service.
| [Versioning](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/versioning.md) | Describes versioning practices for the service |
| [Webhooks](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/webhook.md) | Describes how to use webhooks in the service |
| [Features](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/features.md) | Features currently supported by the service |
| [Authorization](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/authorization.md) | How to setup token authentication and extend for authorization |


## Service Improvement Proposals (SIPs)

Expand All @@ -29,6 +31,7 @@ which DID methods to enable, and which port to listen on. Read the docs below fo
| [TOML Config Files](https://github.com/TBD54566975/ssi-service/blob/main/doc/config/toml.md) | Describes how to use TOML config files |
| [Using a Cloud Key Management Service](https://github.com/TBD54566975/ssi-service/blob/main/doc/config/kms.md) | Describes how to configure a KMS |
| [Storage](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/storage.md) | Describes alternatives for storage by the service |
| [Authentication](https://github.com/TBD54566975/ssi-service/blob/main/doc/service/authorization.md) | Describes how to setup out of the box token authentication |

## API Documentation

Expand Down
20 changes: 20 additions & 0 deletions doc/service/authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Authentication

Out of the box if you set the AUTH_TOKEN to a sha256 token value, then all api calls will require a bearer token that hashes to that. If AUTH_TOKEN is not set then no authentication is required.

Generate a token by hashing the super secure token of `hunter2`:
```sh
export AUTH_TOKEN=$(echo -n "hunter2" | shasum -a 256)
```

Then use `hunter2` as a Bearer token:

```sh
export TOKEN=hunter2
curl -H "Authorization: Bearer $TOKEN" ....
```

# Extending Authentication and Authorization for production environments

The ssi server uses the Gin framework from Golang, which allows various kinds of middleware. Look in `pkg/middleware/Authentication.go` and `pkg/middleware/Authorization.go` for details on how you can wire up authentication and authorization for your use case. One such option is the https://github.com/zalando/gin-oauth2 framework.

33 changes: 24 additions & 9 deletions pkg/server/middleware/Authentication.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package middleware

import (
"crypto/sha256"
"encoding/hex"
"net/http"
"os"

"github.com/gin-gonic/gin"
)
Expand All @@ -17,25 +20,37 @@ func setUpEngine(cfg config.ServerConfig, shutdown chan os.Signal) *gin.Engine {
gin.Logger(),
middleware.Errors(shutdown),
middleware.AuthMiddleware(),
middleware.AuthorizationMiddleware(),
}
*/

func AuthMiddleware() gin.HandlerFunc {
authToken := os.Getenv("AUTH_TOKEN")

return func(c *gin.Context) {
token := c.GetHeader("Authorization")
// This is a dummy check here. You should do your actual JWT token verification.
if token == "IF YOU SET IT TO THIS VALUE IT WILL FAIL" {

// If AUTH_TOKEN is not set, skip the authentication
if authToken == "" {
c.Next()
return
}

// Remove "Bearer " from the token
if len(token) > 7 && token[:7] == "Bearer " {
token = token[7:]
}

// Generate SHA256 hash of the token from the header
hash := sha256.Sum256([]byte(token))
hashedToken := hex.EncodeToString(hash[:])

// Check if the hashed token from the header matches the AUTH token
if hashedToken != authToken {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization is required"})
c.Abort()
return
}
// Assuming that the token is valid and we got the user info from the JWT token.
// You should replace it with actual user info.
user := "user"
c.Set("user", user)

c.Next()
}
}
81 changes: 81 additions & 0 deletions pkg/server/middleware/Authentication_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)

func TestAuthMiddleware(t *testing.T) {
// Set the AUTH_TOKEN environment variable for testing
t.Setenv("AUTH_TOKEN", "f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7") // sha256 hash of "hunter2"

// Create a new gin engine
r := gin.Default()

// Add the AuthMiddleware to the gin engine
r.Use(AuthMiddleware())

// Add a test route
r.GET("/test", func(c *gin.Context) {
c.String(http.StatusOK, "OK")
})

// Create a request with the correct Authorization header
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
req.Header.Add("Authorization", "Bearer hunter2")

// Create a response recorder
w := httptest.NewRecorder()

// Serve the request
r.ServeHTTP(w, req)

// Assert that the status code is 200 OK
assert.Equal(t, http.StatusOK, w.Code)

// Create a request with an incorrect Authorization header
req, _ = http.NewRequest(http.MethodGet, "/test", nil)
req.Header.Add("Authorization", "Bearer nonsense")

// Reset the response recorder
w = httptest.NewRecorder()

// Serve the request
r.ServeHTTP(w, req)

// Assert that the status code is 401 Unauthorized
assert.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestNoAuthMiddleware(t *testing.T) {

t.Setenv("AUTH_TOKEN", "") // no auth token so things just work

// Create a new gin engine
r := gin.Default()

// Add the AuthMiddleware to the gin engine
r.Use(AuthMiddleware())

// Add a test route
r.GET("/test", func(c *gin.Context) {
c.String(http.StatusOK, "OK")
})

// Create a request with the correct Authorization header
req, _ := http.NewRequest(http.MethodGet, "/test", nil)

// Create a response recorder
w := httptest.NewRecorder()

// Serve the request
r.ServeHTTP(w, req)

// Assert that the status code is 200 OK
assert.Equal(t, http.StatusOK, w.Code)

}
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func setUpEngine(cfg config.ServerConfig, shutdown chan os.Signal) *gin.Engine {
gin.Recovery(),
gin.Logger(),
middleware.Errors(shutdown),
middleware.AuthMiddleware(),
}
if cfg.JagerEnabled {
middlewares = append(middlewares, otelgin.Middleware(config.ServiceName))
Expand Down

0 comments on commit 02d6f8f

Please sign in to comment.