Last active
December 29, 2020 21:42
-
-
Save putrikarunia/538a92913a60d1d48a8d99d305705f94 to your computer and use it in GitHub Desktop.
Go cotter auth middleware
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
package middleware | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"io/ioutil" | |
"net/http" | |
"strings" | |
"github.com/google/uuid" | |
"github.com/labstack/echo/v4" | |
"gopkg.in/square/go-jose.v2" | |
"gopkg.in/square/go-jose.v2/jwt" | |
) | |
// 👇 Enter your API KEY ID here | |
const API_KEY_ID = "YOUR_API_KEY_ID" | |
const JWKSURL = "https://www.cotter.app/api/v0/token/jwks" | |
const JWKSLookupKeyID = "SPACE_JWT_PUBLIC:8028AAA3-EC2D-4BAA-BE7A-7C8359CCB9F9" | |
func getKey() ([]byte, error) { | |
// Fetch the JWT Public Key from the URL | |
resp, err := http.Get(JWKSURL) | |
if err != nil { | |
return nil, err | |
} | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return nil, err | |
} | |
// Parse the response into our keys struct | |
keyset := make(map[string][]map[string]interface{}) | |
err = json.Unmarshal(body, &keyset) | |
if err != nil { | |
return nil, err | |
} | |
// It's a Key Set = there might be multiple keys | |
// Find the key with kid = JWKSLookupKeyID | |
if len(keyset["keys"]) <= 0 { | |
return nil, errors.New("Key set is empty") | |
} | |
for _, k := range keyset["keys"] { | |
if k["kid"] == JWKSLookupKeyID { | |
key, err := json.Marshal(k) | |
if err != nil { | |
return nil, err | |
} | |
return key, nil | |
} | |
} | |
return nil, errors.New("Cannot find key with kid") | |
} | |
// validateClientAccessToken validates access token created above | |
func validateClientAccessToken(accessToken string) (map[string]interface{}, error) { | |
tok, err := jwt.ParseSigned(accessToken) | |
if err != nil { | |
return nil, errors.New("Fail parsing access token") | |
} | |
keys, err := getKey() | |
if err != nil { | |
return nil, err | |
} | |
key := jose.JSONWebKey{} | |
key.UnmarshalJSON(keys) | |
token := make(map[string]interface{}) | |
if err := tok.Claims(key, &token); err != nil { | |
return nil, errors.New("Fail parsing access token to claims") | |
} | |
// Check that the aud is our API KEY ID | |
apiKeyID, ok := token["aud"].(string) | |
if !ok { | |
return nil, errors.New("fail asserting aud from jwt.MapClaims") | |
} | |
if apiKeyID != API_KEY_ID { | |
return nil, errors.New("Invalid aud, not meant for this api key id") | |
} | |
return token, nil | |
} | |
// CotterAuth is used to authenticate cotter access_tokens | |
func CotterAuth(next echo.HandlerFunc) echo.HandlerFunc { | |
return func(ctx echo.Context) error { | |
// get the access token from auth header | |
reqToken := ctx.Request().Header.Get("Authorization") | |
splitToken := strings.Split(reqToken, " ") | |
if len(splitToken) != 2 { | |
return errors.New("authorization header malformed") | |
} | |
tokenString := splitToken[1] | |
// Validate that the access token and signature is valid | |
token, err := validateClientAccessToken(tokenString) | |
if err != nil { | |
return echo.NewHTTPError(http.StatusBadRequest, err.Error()) | |
} | |
// Read other claims, such as sub = Cotter User ID | |
uIDStr, ok := token["sub"].(string) | |
if !ok { | |
return echo.NewHTTPError(http.StatusBadRequest, "fail asserting sub from jwt.MapClaims") | |
} | |
cotterUserID, err := uuid.Parse(uIDStr) | |
if err != nil { | |
return echo.NewHTTPError(http.StatusBadRequest, "invalid user id format in jwt claims") | |
} | |
fmt.Println("User logging in = ", cotterUserID) | |
return next(ctx) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment