I am trying to use vk auth with martini. But have error on compile:
/goPath/vkAuthTry2.go:38: undefined: YourRedirectFunc
The question is how to define YourRedirectFunc function. Or if ask more widely I need working example of martini app with vk social network authentication or if even more widely an example of any golang website using vk authentication.
Full code:
package main
import (
"github.com/go-martini/martini"
"github.com/yanple/vk_api"
"net/http"
)
var api vk_api.Api
func prepareMartini() *martini.ClassicMartini {
m := martini.Classic()
m.Get("/somePage", func(w http.ResponseWriter, r *http.Request) {
// And receive token on the special method (redirect uri)
currentUrl := r.URL.RequestURI() // for example "yoursite.com/get_access_token#access_token=3304fdb7c3b69ace6b055c6cba34e5e2f0229f7ac2ee4ef46dc9f0b241143bac993e6ced9a3fbc111111&expires_in=0&user_id=1"
accessToken, userId, expiresIn, err := vk_api.ParseResponseUrl(currentUrl)
if err != nil {
panic(err)
}
api.AccessToken = accessToken
api.UserId = userId
api.ExpiresIn = expiresIn
w.Write([]byte("somePage"))
})
return m
}
func main() {
authUrl, err := api.GetAuthUrl(
"domain.com/method_get_access_token", // redirect URI
"token", // response type
"4672050", // client id
"wall,offline", // permissions https://vk.com/dev/permissions
)
if err != nil {
panic(err)
}
YourRedirectFunc(authUrl)
prepareMartini().Run()
}
Update
I edited my code according to #Elwinar's answer:
package main
import (
"fmt"
"github.com/go-martini/martini"
"github.com/yanple/vk_api"
"net/http"
)
var api vk_api.Api
func prepareMartini() *martini.ClassicMartini {
m := martini.Classic()
// This handler redirect the request to the vkontact system, which
// will perform the authentification then redirect the request to
// the URL we gave as the first paraemeter of the GetAuthUrl method
// (treated by the second handler)
m.Get("/vk/auth", func(w http.ResponseWriter, r *http.Request) {
var api vk_api.Api
authUrl, err := api.GetAuthUrl("http://localhost:3000/vk/token", "token", "4672050", "wall,offline")
if err != nil {
panic(err)
}
http.Redirect(w, r, authUrl, http.StatusFound)
})
// This handler is the one that get the actual authentification
// information from the vkontact api. You get the access token,
// userid and expiration date of the authentification session.
// You can do whatever you want with them, generally storing them
// in session to be able to get the actual informations later using
// the access token.
m.Get("/vk/token", func(w http.ResponseWriter, r *http.Request) {
accessToken, userId, expiresIn, err := vk_api.ParseResponseUrl(r.URL.String())
if err != nil {
panic(err)
}
fmt.Println(accessToken)
fmt.Println(userId)
fmt.Println(expiresIn)
})
return m
}
func main() {
prepareMartini().Run()
}
now no complie errors, but still cannot login.
When I opened http://localhost:3000/vk/auth I was redirected on page...
https://oauth.vk.com/authorize?client_id=MY_APP_ID&redirect_uri=localhost%3A3000%2Fvk%2Ftoken&response_type=token&scope=wall%2Coffline
... and got the following browser output:
{"error":"invalid_request","error_description":"redirect_uri is incorrect, check application domain in the settings page"}
Of course instead of 4672050 I pasted my app id. This app was specially generated for localhost:3000.
Maybe I need to paste somewhere my private key for oauth like pYFR2Xojlkad87880dLa.
Update 2
#qwertmax's answer almost works. I have successfully logged in by vk, but my code prints empty lines instead of userId and another user information:
accessToken, userId, expiresIn, err := vk_api.ParseResponseUrl(r.URL.String())
fmt.Println(accessToken)
fmt.Println(userId)
fmt.Println(expiresIn)
package main
import (
"fmt"
"github.com/go-martini/martini"
"github.com/yanple/vk_api"
"net/http"
)
var api vk_api.Api
func prepareMartini() *martini.ClassicMartini {
m := martini.Classic()
m.Get("/vk/auth", func(w http.ResponseWriter, r *http.Request) {
var api vk_api.Api
authUrl, err := api.GetAuthUrl("http://localhost:3000/vk/token", "token", "2756549", "wall,offline")
fmt.Println(authUrl)
if err != nil {
panic(err)
}
http.Redirect(w, r, authUrl, http.StatusFound)
})
m.Get("/vk/token", func(w http.ResponseWriter, r *http.Request) {
accessToken, userId, expiresIn, err := vk_api.ParseResponseUrl(r.URL.String())
if err != nil {
panic(err)
}
fmt.Println(accessToken)
fmt.Println(userId)
fmt.Println(expiresIn)
})
return m
}
func main() {
prepareMartini().Run()
}
for you VK settings you have to add your domain like on this screenshot
After that you will be redirected to http://localhost:3000/vk/token#access_token=some_token&expires_in=0&user_id=0000000
but I'm not sure how you will Parse url via "ParseResponseUrl" because vk get to you "fragment url".
Fragment url is not sending to serve via HTTP - I think that could be a problem.
Thanks for answer,
I added example #qwertmax and fixed bug for parse fragment of url. Please update package and see example.
package main
// Thanks #qwertmax for this example
// (http://stackoverflow.com/questions/29359907/social-network-vk-auth-with-martini)
import (
"log"
"github.com/go-martini/martini"
"github.com/yanple/vk_api"
"net/http"
)
var api vk_api.Api
func prepareMartini() *martini.ClassicMartini {
m := martini.Classic()
m.Get("/vk/auth", func(w http.ResponseWriter, r *http.Request) {
authUrl, err := api.GetAuthUrl(
"http://localhost:3000/vk/token",
"app client id",
"wall,offline")
if err != nil {
panic(err)
}
http.Redirect(w, r, authUrl, http.StatusFound)
})
m.Get("/vk/token", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
err := api.OAuth(
"http://localhost:3000/vk/token", // redirect uri
"app secret key",
"app client id",
code)
if err != nil {
panic(err)
}
http.Redirect(w, r, "/", http.StatusFound)
})
m.Get("/", func(w http.ResponseWriter, r *http.Request) string {
if api.AccessToken == "" {
return "<a href='/vk/auth'>Авторизоваться</a>"
}
// Api have: AccessToken, UserId, ExpiresIn
log.Println("[LOG] martini.go:48 ->", api.AccessToken)
// Make query
params := make(map[string]string)
params["domain"] = "yanple"
params["count"] = "1"
strResp, err := api.Request("wall.get", params)
if err != nil {
panic(err)
}
return strResp
})
return m
}
func main() {
prepareMartini().Run()
}
Update 1:
Update your package with command: go get -u github.com/yanple/vk_api
Thanks for the comment.
In the package documentation, YourRedirectFunc is intended as placeholder for the actual method used to redirect a request in your particular case. It could be http.Redirect for example. Same for getCurrentUrl.
In fact, the example you linked to should be written as two handlers for your martini instance:
// This handler redirect the request to the vkontact system, which
// will perform the authentification then redirect the request to
// the URL we gave as the first paraemeter of the GetAuthUrl method
// (treated by the second handler)
m.Get("/vk/auth", func(w http.ResponseWriter, r *http.Request) {
var api vk_api.Api
authUrl, err := api.GetAuthUrl("domain.com/vk/token", "token", "4672050", "wall,offline")
if err != nil {
panic(err)
}
http.Redirect(w, r, authUrl, http.Found)
})
// This handler is the one that get the actual authentification
// information from the vkontact api. You get the access token,
// userid and expiration date of the authentification session.
// You can do whatever you want with them, generally storing them
// in session to be able to get the actual informations later using
// the access token.
m.Get("/vk/token", func(w http.ResponseWriter, r *http.Request) {
accessToken, userId, expiresIn, err := vk_api.ParseResponseUrl(r.URL.String())
if err != nil {
panic(err)
}
})
Related
I'm new to golang, and have followed this (https://auth0.com/blog/authentication-in-golang/) auth0 guide, for setting up a go rest api.
I'm struggeling with converting to Fiber, and in the same time putting my functions that are being called by routes, out to seperate files.
Currently my main file looks like this:
func main() {
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.Dir("./views/")))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
r.Handle("/posts", config.JwtMiddleware.Handler(GetPosts)).Methods("GET")
//r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")
// For dev only - Set up CORS so React client can consume our API
corsWrapper := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"},
})
http.ListenAndServe(":8080", corsWrapper.Handler(r))
}
var GetPosts= http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
collection, err := config.GetMongoDbCollection(dbName, collectionName)
if err != nil {
fmt.Println("Error")
}else{
fmt.Println(collection)
//findOptions := options.Find()
cursor, err := collection.Find(context.Background(), bson.M{})
if err != nil {
log.Fatal(err)
}
var posts[]bson.M
if err = cursor.All(context.Background(), &posts); err != nil {
log.Fatal(err)
}
fmt.Println(posts)
payload, _ := json.Marshal(posts)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}
})
So I would like to convert from: r := mux.NewRouter() to fiber and in the same time move my GetPosts function out in a seperate file. When doing this, I can't seem to continue calling my jwtMiddleware.
I have tried this package: https://github.com/Mechse/fiberauth0 but it seems like its broken. At least I can call protected routes without supplying jwt tokens in my header.
You can simply convert 'net/http' style middleware handlers with the provided adaptor package(https://github.com/gofiber/adaptor). Note you need to make some changes to the function signature provided by auth0 but this works;
// EnsureValidToken is a middleware that will check the validity of our JWT.
func ensureValidToken(next http.Handler) http.Handler {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to set up the jwt validator")
}
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"Failed to validate JWT."}`))
}
middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)
return middleware.CheckJWT(next)
}
var EnsureValidToken = adaptor.HTTPMiddleware(ensureValidToken)
app := fiber.New()
app.Use(EnsureValidToken)
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
This code has two parts. One is to set and clear the session and the second part is login and logout.
What it does?
In the second part, If an email and password are found in the database and the match is true then it set the session and move to the about() function which has an about file. If the logout is called then it clears the session and redirects to the home page.
What it should do?
The problem is that even if I am logged out and the session is cleared, I can still visit an about page. I don't want to be allowed to visit an about page if I am not logged in.
First part
var cookieHandler = securecookie.New(
securecookie.GenerateRandomKey(64),
securecookie.GenerateRandomKey(32),
)
func setSession(email, password string, res http.ResponseWriter) {
value := map[string]string{ "email": email, "password": password}
encoded, err := cookieHandler.Encode("session", value)
if err == nil {
cookie := &http.Cookie{ Name: "session", Value: encoded, Path: "/"}
http.SetCookie(res, cookie)
}
}
func clearSession(res http.ResponseWriter) {
cookie := &http.Cookie{ Name: "session", Value: "", Path: "/", MaxAge: -1}
http.SetCookie(res, cookie)
}
Second part
func about(res http.ResponseWriter, req *http.Request) {
if err := tmpl.ExecuteTemplate(res, "about.html", nil); err != nil {
log.Fatal("template didn't execute", nil)
}
}
func loginAuth(res http.ResponseWriter, req *http.Request) {
email := req.FormValue("email")
password := req.FormValue("password")
match := database.Findaccount(email, password)
if match == true {
setSession(email, password, res)
about(res, req)
fmt.Println("You're logged in")
} else {
tmpl.ExecuteTemplate(res, "login.html", nil)
fmt.Println("Enter the correct email or password")
}
}
func logout(res http.ResponseWriter, req *http.Request) {
clearSession(res)
http.Redirect(res, req, "/", 302)
}
Few things you don't want to do, in general:
Don't use cookie encoder directly. Use a cookie session store.
Don't call an handler within an handler, prefer a redirect. This should prevent writing twice the headers/body on the response.
Don't pass the user/password in the cookie, even encoded, in 2021 we may even prevent sending that through the form at all (you might consider sending only a hash and re hash the hash on the server side to figure out if things are good to go).
Few things you always want to do:
Write tests.
Make use of middlewares.
Always provide small reproducible examples.
That being said, I have written a lookalike code with some stubs (mostly for db), I removed template support (i was not in the mood to write HTML) and more importantly I wrote tests !!
To the question How to clear the session :
Delete the values from the store, write the store
To the question and only visit an about page after login?:
Wrap that handler with a middleware that verifies login data attached to the user cookie store
-- main.go --
package main
import (
"crypto/sha256"
"encoding/gob"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
)
// Note: Don't store your key in your source code. Pass it via an
// environmental variable, or flag (or both), and don't accidentally commit it
// alongside your code. Ensure your key is sufficiently random - i.e. use Go's
// crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
var store = sessions.NewCookieStore(
securecookie.GenerateRandomKey(32),
)
//emulate db package
func dbLookupUser(user, pwd string) bool {
return user == "user" && pwd == "pwd"
}
func dbLookupHash(h string) bool {
return h == hash("user", "pwd")
}
func hash(s ...interface{}) string {
hr := sha256.New()
fmt.Fprint(hr, s...)
return fmt.Sprintf("%x", hr.Sum(nil))
}
// hashKey is a typed key for the session map store to prevent unintented overwrites.
type hashKey string
func init() {
gob.Register(hashKey(""))
}
func loginAuth(res http.ResponseWriter, req *http.Request) {
email := req.FormValue("email")
password := req.FormValue("password")
match := dbLookupUser(email, password)
if match == true {
session, _ := store.Get(req, "session-name")
session.Values["hash"] = hash(email, password)
// Save it before we write to the response/return from the handler.
err := session.Save(req, res)
if err == nil {
// about(res, req) // don't!
// the about handler might want to setup its own http response headers
// That would conflict with what we did here.
// prefer a redirect
http.Redirect(res, req, "/about", http.StatusFound)
return
}
} else {
fmt.Fprintf(res, "try again") // use a templatee instead!
// tmpl.ExecuteTemplate(res, "login.html", nil)
}
}
func logout(res http.ResponseWriter, req *http.Request) {
session, _ := store.Get(req, "session-name")
delete(session.Values, hashKey("hash"))
_ = session.Save(req, res)
http.Redirect(res, req, "/", 302)
}
func about(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "welcome to about page")
}
func requireLogin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
var h string
if x, ok := session.Values[hashKey("hash")]; ok {
h = x.(string)
}
var match bool
if h != "" {
match = dbLookupHash(h)
}
if !match {
// Write an error and stop the handler chain
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next(w, r)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", loginAuth)
r.HandleFunc("/logout", logout)
r.HandleFunc("/about", requireLogin(about))
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
-- main_test.go --
package main
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestLogin(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
form := url.Values{}
form.Set("email", "user")
form.Set("password", "pwd")
req.Form = form
w := httptest.NewRecorder()
loginAuth(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusFound; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestLoginFailure(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
form := url.Values{}
form.Set("email", "!user")
form.Set("password", "!pwd")
req.Form = form
w := httptest.NewRecorder()
loginAuth(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusOK; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestAboutNotLogged(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
w := httptest.NewRecorder()
requireLogin(about)(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusForbidden; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestAboutLogged(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
w := httptest.NewRecorder()
session, _ := store.Get(req, "session-name")
session.Values[hashKey("hash")] = hash("user", "pwd")
err := session.Save(req, w)
if err != nil {
t.Fatal(err)
}
hdr := w.Header()
req.Header.Add("Cookie", hdr["Set-Cookie"][0])
w = httptest.NewRecorder()
requireLogin(about)(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusOK; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
I would like to ask if we can create 'middleware' functions for Go http client? Example I want to add a log function, so every sent request will be logged, or add setAuthToken so the token will be added to each request's header.
You can use the Transport parameter in HTTP client to that effect, with a composition pattern, using the fact that:
http.Client.Transport defines the function that will handle all HTTP requests;
http.Client.Transport has interface type http.RoundTripper, and can thus be replaced with your own implementation;
For example:
package main
import (
"fmt"
"net/http"
)
// This type implements the http.RoundTripper interface
type LoggingRoundTripper struct {
Proxied http.RoundTripper
}
func (lrt LoggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
// Do "before sending requests" actions here.
fmt.Printf("Sending request to %v\n", req.URL)
// Send the request, get the response (or the error)
res, e = lrt.Proxied.RoundTrip(req)
// Handle the result.
if (e != nil) {
fmt.Printf("Error: %v", e)
} else {
fmt.Printf("Received %v response\n", res.Status)
}
return
}
func main() {
httpClient := &http.Client{
Transport: LoggingRoundTripper{http.DefaultTransport},
}
httpClient.Get("https://example.com/")
}
Feel free to alter names as you wish, I did not think on them for very long.
I worked on a project that had similar requirement so I built a middleware pipeline library that allows setting multiple middleware to the http client. You can check it out here.
Using the library, you would solve this in the following way
type LoggingMiddleware struct{}
func (s LoggingMiddleware) Intercept(pipeline pipeline.Pipeline, req *http.Request) (*http.Response, error) {
body, _ := httputil.DumpRequest(req, true)
log.Println(fmt.Sprintf("%s", string(body)))
/*
If you want to perform an action based on the response, do the following
resp, err = pipeline.Next
// perform some action
return resp, err
*/
return pipeline.Next(req)
}
transport := pipeline.NewCustomTransport(&LoggingMiddleware{})
client := &http.Client{Transport: transport}
resp, err := client.Get("https://example.com")
if err != nil {
// handle err
}
fmt.Println(resp.Status)
I wrote a small tutorial/library to do just that https://github.com/HereMobilityDevelopers/mediary
Here is some basic usage example:
client := mediary.Init().AddInterceptors(dumpInterceptor).Build()
client.Get("https://golang.org")
func dumpInterceptor(req *http.Request, handler mediary.Handler) (*http.Response, error) {
if bytes, err := httputil.DumpRequestOut(req, true); err == nil {
fmt.Printf("%s", bytes)
//GET / HTTP/1.1
//Host: golang.org
//User-Agent: Go-http-client/1.1
//Accept-Encoding: gzip
}
return handler(req)
}
There is also an explanation here https://github.com/HereMobilityDevelopers/mediary/wiki/Reasoning
Good idea! Here is a simple implementation of HTTP service middleware in Go.
Usually a simple http service framework is to register a bunch of routes, and then call different logics to process them according to the routes.
But in fact, there may be some unified processing involving almost all routes, such as logs, permissions, and so on.
So it is a good idea to engage in intermediate preprocessing at this time.
Define a middleware unit:
package main
import (
"net/http"
)
// AdaptorHandle middleware func type
type AdaptorHandle func(w http.ResponseWriter, r *http.Request) (next bool, err error)
// MiddleWareAdaptor router middlewares mapped by url
type MiddleWareAdaptor struct {
URLs map[string][]AdaptorHandle
}
// MakeMiddleWareAdaptor make a middleware adaptor
func MakeMiddleWareAdaptor() *MiddleWareAdaptor {
mwa := &MiddleWareAdaptor{
URLs: make(map[string][]AdaptorHandle),
}
return mwa
}
// Regist regist a adaptor
func (mw *MiddleWareAdaptor) Regist(url string, Adaptor ...AdaptorHandle) {
for _, adp := range Adaptor {
mw.URLs[url] = append(mw.URLs[url], adp)
// mw.URLs[url] = adp
}
}
// Exec exec middleware adaptor funcs...
func (mw *MiddleWareAdaptor) Exec(url string, w http.ResponseWriter, r *http.Request) (bool, error) {
if adps, ok := mw.URLs[url]; ok {
for _, adp := range adps {
if next, err := adp(w, r); !next || (err != nil) {
return next, err
}
}
}
return true, nil
}
Then wrap the route processing function with a middleware entry:
func middlewareHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// before call handler
start := time.Now()
do, _ := mwa.Exec(r.URL.Path, w, r) // exec middleware
// call next handler
if do {
log.Println("middleware done. next...")
next.ServeHTTP(w, r)
} else {
log.Println("middleware done.break...")
}
// after call handle
log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
})
}
mux.Handle("/", middlewareHandler(&uPlusRouterHandler{}))
type uPlusRouterHandler struct {
}
func (rh *uPlusRouterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
}
Finally, register the middleware you need:
mwa = MakeMiddleWareAdaptor() // init middleware
mwa.Regist("/", testMWAfunc, testMWAfunc2) // regist middleware
...
func testMWAfunc(w http.ResponseWriter, r *http.Request) (bool, error) {
log.Println("I am Alice Middleware...")
log.Printf("Started %s %s", r.Method, r.URL.Path)
return true, nil
}
func testMWAfunc2(w http.ResponseWriter, r *http.Request) (bool, error) {
log.Println("I am Ben Middleware...")
return false, nil // return false,break follow-up actions.
}
This can be achieved using closure functions. It's probably more clear with an example:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", logged(hello))
http.ListenAndServe(":3000", nil)
}
func logged(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Println("logging something")
f(w, r)
fmt.Println("finished handling request")
}
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "<h1>Hello!</h1>")
}
credit goes to: http://www.calhoun.io/5-useful-ways-to-use-closures-in-go/
I'm trying the JWT middleware examples to get JWT to work with Martini and it's giving me an return when it hits the authentication handler.
Here's the code, straight from the examples..
package main
import (
"encoding/json"
"github.com/auth0/go-jwt-middleware"
"github.com/dgrijalva/jwt-go"
"github.com/go-martini/martini"
"net/http"
)
func main() {
StartServer()
}
func StartServer() {
m := martini.Classic()
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte("My Secret"), nil
},
SigningMethod: jwt.SigningMethodHS256,
})
m.Get("/ping", PingHandler)
m.Get("/secured/ping", jwtMiddleware.CheckJWT, SecuredPingHandler)
m.Run()
}
type Response struct {
Text string `json:"text"`
}
func respondJson(text string, w http.ResponseWriter) {
response := Response{text}
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}
func PingHandler(w http.ResponseWriter, r *http.Request) {
respondJson("All good. You don't need to be authenticated to call this", w)
}
func SecuredPingHandler(w http.ResponseWriter, r *http.Request) {
respondJson("All good. You only get this message if you're authenticated", w)
}
If I hit the unauthenticated route, it works. If I hit the authenticated route, it tells me I need a token. If I hit the authenticated route with a token that's not right, I get an error stating,
signature is invalid
<*errors.errorString Value>
So all that works. If I hit the authenticated route with the right token, it gives me as the return text. I can't find out why. This is straight from their example, though I get the same in my personal project.
Instead of
m.Get("/secured/ping", jwtMiddleware.CheckJWT, SecuredPingHandler)
Try this instead
m.Get("/secured/ping", jwtMiddleware.Handler(SecuredPingHandler))
I have a registration page with two handlers, one for displaying the form, one for processing a form submission.
I am trying to use a session.AddFlash method to save an error, then do 302 redirect back to the registration form and display the error.
I set up a session store:
package web
import (
"github.com/gorilla/sessions"
)
var sessionStore = sessions.NewCookieStore([]byte(sessionSecret))
Then my handlers look like this:
package web
import (
"html/template"
"log"
"net/http"
)
func registerForm(w http.ResponseWriter, r *http.Request) {
session, err := sessionStore.Get(r, "mysession")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := map[string]interface{}{}
log.Print("Flashes: ")
log.Print(session.Flashes())
if flashes := session.Flashes(); len(flashes) > 0 {
data["error"] = flashes[0]
}
tmpl, _ := template.ParseFiles("web/templates/register.html.tmpl")
tmpl.Execute(w, data)
}
func register(w http.ResponseWriter, r *http.Request) {
session, err := sessionStore.Get(r, "mysession")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
r.ParseForm()
username := r.Form["username"][0]
password := r.Form["password"][0]
if UserExists(username) {
log.Print("Username already taken")
session.AddFlash("Username already taken")
http.Redirect(w, r, "/web/register", http.StatusFound)
return
}
_, err = CreateUser(username, password)
log.Print(err)
if err != nil {
session.AddFlash(err.Error())
http.Redirect(w, r, "/web/register", http.StatusFound)
return
}
http.Redirect(w, r, "/web/login", http.StatusFound)
}
By adding logs I can see that UserExists returns true therefor a flash message should be added however after redirection to the form handler there is no flash message saved in the session.
I think you have to save the session before you redirect.
session.Save(r, w)
http://www.gorillatoolkit.org/pkg/sessions#Session.Save