go-restful + JWT authentication - rest

I'm trying to plug JWT authentication within a very simple go service written with go-restful.
The code is very similar to:
package main
import (
"github.com/emicklei/go-restful"
"log"
"net/http"
)
type User struct {
Id, Name string
}
type UserList struct {
Users []User
}
func getAllUsers(request *restful.Request, response *restful.Response) {
log.Printf("getAllUsers")
response.WriteEntity(UserList{[]User{{"42", "Gandalf"}, {"3.14", "Pi"}}})
}
func NewUserService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.GET("").To(getAllUsers))
return ws
}
func main() {
restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
where restful.Request is a wrapper around http.Request.
That being said, it might be possible to use the Auth0 jwt middleware.
But as a golang newbie, I'm a bit lost in the plumbing process. I see that I must use a Filter function like
ws.Filter(jwtAuthentication)
where
func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
// Jwt Magic goes here \o
chain.ProcessFilter(req, resp)
}
But I don't figure how and where should I instanciate the JWT middleware.

Here is the example of filter implementation using auth0/go-jwt-middleware. In real life you probably want to avoid creating new instance of jwtMiddleware every time.
func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte("My Secret"), nil
},
SigningMethod: jwt.SigningMethodHS256,
})
if err := jwtMiddleware.CheckJWT(resp.ResponseWriter, req.Request); err != nil {
logger.Errorf("Authentication error: %v", err)
}
chain.ProcessFilter(req, resp)
}
After the filter the parsed token will be in the context (auth0/go-jwt-middleware uses gorilla/context). Default context key is user.
Note: when JWTMiddleware.SigningMethod is set, the middleware verifies that tokens are signed with the specific signing algorithm.
If the signing method is not constant, the ValidationKeyGetter callback can be used to implement additional checks.
Important to avoid security issues described here.

Here is example of Login API to generate Token, and JWT Authentication filter to check authentication
import (
"os"
"strings"
"github.com/dgrijalva/jwt-go"
"golang.org/x/crypto/bcrypt"
)
type Token struct {
UserId uint
Username string
jwt.StandardClaims
}
type Account struct {
ID uint
Email string
Password string
Token string
}
func Login(request *restful.Request, response *restful.Response) {
account := &Account{ID: 1, Email: "test#email.com" }
// TODO - account should be pulled from database
tk := &Token{ UserId: account.ID }
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
tokenString, _ := token.SignedString([]byte("JWT-SECRET-GOES-RIGHT-HERE"))
account.Token = tokenString
account.Password = ''
response.WriteEntity(account)
}
func JwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
tokenHeader := req.Request.HeaderParameter("Authorization")
if tokenHeader == "" {
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
splitted := strings.Split(tokenHeader, " ")
if len(splitted) != 2 {
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
tokenPart := splitted[1]
tk := &Token{}
token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
return []byte("JWT-SECRET-GOES-RIGHT-HERE"), nil
})
if err != nil { //Malformed token, returns with http code 403 as usual
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
if !token.Valid { //Token is invalid, maybe not signed on this server
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
chain.ProcessFilter(req, resp)
}
And then apply filter
ws.Filter(JwtAuthentication)

Related

Receive Consumption REQUEST NOTIFY and Send Consumption Info

apple will send a Consumption_Request notify to merchant server if user start a refund.
When I tried to process the Consumption Request notify and call the Send Consumption Info interface to send user consumption info to apple server, I got two forms of response.
case 1 :
when my param is wrong , the response like this :enter image description here
case 2 :
when my param is right , the response code is 401 ,like this : enter image description here
And I found 401 mean Unauthorized . So maybe my JWT check failed.
SO my question is how do you generate your JWT token? Is any demo ?
and how do you get your secret key file(the .p8 file. I suspect that I got a wrong file)?
how do you generate your JWT token?
here is one sample by golang
func readPrivateKeyFromFile(keyFile string) (*ecdsa.PrivateKey, error) {
bytes, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
block, _ := pem.Decode(bytes)
if block == nil {
return nil, errors.New("appstore private key must be a valid .p8 PEM file")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
switch pk := key.(type) {
case *ecdsa.PrivateKey:
return pk, nil
default:
return nil, errors.New("appstore private key must be of type ecdsa.PrivateKey")
}
}
func generateToken(privateKey *ecdsa.PrivateKey) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"iss": "issuerId,
"isa": time.Now().Unix(),
"exp": expireTIme,
"aud": "appstoreconnect-v1",
"nonce": "uuid",
"bid": "bid",
})
token.Header["kid"] = "keyId"
token.Header["alg"] = "ES256"
token.Header["typ"] = "JWT"
return token.SignedString(privateKey)
}
how do you get your secret key file(the .p8 file. I suspect that I got a wrong file)?
Please refer to doc https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api?changes=latest_major

Unable to resty.v2 POST call via cron tab

I'm creating a scheduler using Golang's crontab and resty.v2 to call a POST api(Dropbox file upload api).
When I'm invoking the file upload method manually its working fine. But when the same method is invoked via the crontab scheduler its not making the rest call.
Interesting fact is that, it's neither throwing any error nor providing any rest call response.
PFB the scheduler code to invoke the upload method:
func StartJob() {
ctab := crontab.New()
for _, v := range strings.Split(os.Getenv("schedules"), commaSeparator) {
match, _ := regexp.MatchString(regex, v)
if match == true {
hhmm := strings.Split(v, colonSeparator)
expresion := fmt.Sprintf(cronExpression, hhmm[1], hhmm[0])
ctab.MustAddJob(expresion, func() {
go service.Upload(dropboxEndpoint, dropboxAccessToken, os.Getenv("hkpath"))
})
} else {
fmt.Printf("Not acceptable time-format : %s\n", v)
}
}}
And here is the upload method code:
func Upload(dropboxEndpoint string, dropboxAccessToken string, path string) {
_, fileName := filepath.Split(path)
fileBytes, fileErr := ioutil.ReadFile(path)
if fileErr == nil {
fmt.Println(path)
resp, restErr := client.R().
SetBody(fileBytes).
SetContentLength(true).
SetHeader("Authorization", fmt.Sprintf("Bearer %s", dropboxAccessToken)).
SetHeader("Dropbox-API-Arg", fmt.Sprintf("{\"path\": \"/home/f1/%s/%s\",\"mode\": \"add\",\"autorename\": true,\"mute\": false,\"strict_conflict\": false}", os.Getenv("name"), fileName)).
SetHeader("Content-Type", "application/octet-stream").
Post(dropboxEndpoint)
if restErr != nil {
log.Fatal(restErr)
} else {
fmt.Println(resp)
}
} else {
log.Fatal(fileErr)
}}
Any idea what's the wrong I'm doing?

Generic REST API Golang

Searching SO for Generic REST API Golang gives 0 results. Searching Google gives 2 results. So this question is maybe not correctly formulated or it is impossible to achieve in Golang.
My goal is to avoid repeating similar code over and over again. So I am trying to make the code in Golang as generic as possible. Write once, use many.
This is my first attempt to create a generic REST API for select in Golang. The code below gives almost what I want:
But the result is presented in the Terminal. I have no idea how to redirect the result to the browser.
package main
import (
"fmt"
"log"
"net/http"
"database/sql"
"time"
_ "github.com/lib/pq"
)
var db *sql.DB
func main() {
Connect()
http.HandleFunc("/", Query)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func Connect() {
const (
host = "127.0.0.1"
port = 5432
user = "test"
password = "test"
dbname = "Test")
login := fmt.Sprintf("host=%s port=%d user=%s "+"password=%s dbname=%s sslmode=require", host, port, user, password, dbname)
var err error
db, err = sql.Open("postgres", login)
if err != nil {
log.Fatalln(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
func Query(w http.ResponseWriter, r *http.Request) {
var query string
switch r.URL.String() {
case "/getuser":
query = "select * from getuser()"
case "/getco":
query = "select * from getco()"
case "/etc"
query = "select * from etc"
default:
query = ""
}
var err error
var rows *sql.Rows
rows, err = db.Query(query)
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
defer rows.Close()
cols, err := rows.Columns()
vals := make([]interface{}, len(cols))
for i := 0; i < len(cols); i++ {
vals[i] = new(interface{})
if i != 0 {
fmt.Print("\t")
}
fmt.Print(cols[i])
}
fmt.Println()
for rows.Next() {
err = rows.Scan(vals...)
if err != nil {
fmt.Println(err)
continue
}
for i := 0; i < len(vals); i++ {
if i != 0 {
fmt.Print("\t")
}
printValue(vals[i].(*interface{}))
}
fmt.Println()
}
func printValue(pval *interface{}) {
switch v := (*pval).(type) {
case nil:
fmt.Print("NULL")
case bool:
if v {
fmt.Print("1")
} else {
fmt.Print("0")
}
case []byte:
fmt.Print(string(v))
case time.Time:
fmt.Print(v.Format("2006-01-02"))
default:
fmt.Print(v)
}
}
Every attempt to write to the browser gives various type of errors:
fmt.Printf("%s\n", vals...)
My questions are
How do I redirect the result to the browser?
Is there any better way to achieve this? (reuse generic code)
My recommendation would be to look at using existing packages like "mux" for calling REST APIs in browser. As a quick demo how you would do it as as follows:
your restapi.go cound have APIs as follows:
func SampleAPI(w http.ResponseWriter, r *http.Request) { //Assuming this is a POST request
var example SomeSruct
_ = json.NewDecoder(r.Body).Decode(&example) //Decode the POST body
result := someLogicFunction(example) //call your generic function
json.NewEncoder(w).Encode(result) //encode the result to pass it back to browser
}
Now say you write a main.go and you are using mux package here is an example of how you would call this
main.go
func main() {
router := mux.NewRouter()
router.HandleFunc("/testFunc",restapi.SampleAPI).Methods("POST") //This creates the route for your http request
handler := cros.Default().Handler(router) //You will need this if you plan to deploy it in a server and call it externally for testing locally you don't need this
log.Fatal(http.ListenAndServe(":8080", handler)) //Port that the router is listening to
}
Now note that you will have to import the "github.com/gorilla/mux" and the "github.com/rs/cors" packages to use these but this way you can create REST APIs whic can be accessed by te browser. Similarly you could create a GET method and use parameters which you can grab in your function and perform any logical step.
If you build and install the above code you can POST to localhost:8080/testFunc over http using any web app and get results i your browser. If you had a GET request you could directly type the Url in the browser and see the result.
write response with appropriahe HTTP hearers && status code
import "net/http"
func writeResponse(w http.ResponseWriter, contents []byte) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, contents)
}
sounds a little unclear, sorry

Submitting form with golang http library

Oke, I'm currently trying to login in to my school website, with my own Crawler. Altough they have some protection against login. First I do a Get request to the Website so I get the token from the hidden Input field. That token I use in my next Post request to login to the url! But for some reason the http response is that I cannot resubmit the form. But with doing the same in Postman rest client (chrome plugin) I can login!
When I try to submit a form to this url:
postLoginUrl = "?username=%s&password=%s&submit=inloggen&_eventId=submit&credentialsType=ldap&lt=%s"
loginUrl = "https://login.hro.nl/v1/login"
where %s are filled in credentials
req, err := client.Post(loginUrl, "application/x-www-form-urlencoded", strings.NewReader(uri))
I'm getting as response that the Form cannot be resubmitted.
But when I try it with Postman rest client, I'm allowed to login.
code for Csrf token:
func getCSRFtoken() (key string) {
doc, err := goquery.NewDocument(loginUrl)
if err != nil {
log.Fatal(err)
}
types := doc.Find("input")
for node := range types.Nodes {
singlething := types.Eq(node)
hidden_input, _ := singlething.Attr("type")
if hidden_input == "hidden" {
key, _ := singlething.Attr("value")
return key
}
}
return ""
}
goquery.NewDocument is a http.Get()
My question now is, how does the URL get's formatted from the library
Maybe you would be better off using:
(c *Client)PostForm(url string, data url.Values) (resp *Response, err error)
from net/http like http://play.golang.org/p/8D6XI6arkz
With the params in url.Values (instead of concatenating the strings, like you are doing now.)

Using a global variable in golang

I have a global variable that I am trying to use across two different functions, and unable to figure out why the following code is not working...
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net"
"net/http"
)
type Message struct {
Body string
}
var api rest.Api
func hostLookup(w rest.ResponseWriter, req *rest.Request) {
ip, err := net.LookupIP(req.PathParam("host"))
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&ip)
}
func foo() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
&rest.Route{"GET", "/lookup/#host", hostLookup},
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
}
func bar() {
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
func main() {
foo()
bar()
}
The above code does not work... the HTTP server does not route the request to the hostLookup function.
However - if I move the following line from bar()
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
to the end of function foo(), then it works correctly
What am I doing wrong?
Your problem is two fold...
For one, you declare
var api rest.Api
but the rest.New() returns a *rest.Api
func NewApi() *Api {
Secondly, in your foo() function, you are creating a local variable called api instead of using your package variable.
Instead of
api := rest.NewApi()
It should be
api = rest.NewApi()
So, the fix is to add a * before rest.Api as in var api *rest.Api and remove a colon from the setting of api as in api = rest.NewApi()