-
-
Notifications
You must be signed in to change notification settings - Fork 52
Authentication
You can check for authentication and force-close a client at any state of your application, on Server.OnConnect
or through event callbacks for example. The recommended way is to do that on the HTTP handshake, before upgrade to the websocket subprotocol, but it really depends on your app's flow and requirements.
Let's see three small examples here, two with net/http
and one more real-world example using JWT authentication and Iris.
The first one is simple enough, it does the authentication before the websocket server's handler execution, through a middleware using the std net/http
library.
Let's assume that the isAuthenticated(r *http.Request) bool
function is our method to check if a client request is authenticated or not.
// [websocketServer := neffos.New...]
router := http.NewServeMux()
router.HandleFunc("/websocket_endpoint", func(w http.ResponseWriter, r *http.Request){
if !isAuthenticated(r) {
statusCode := http.StatusUnauthorized // 401.
http.Error(w, http.StatusText(statusCode), statusCode)
return
}
// if succeed continue to our websocket app.
websocketServer.ServeHTTP(w,r)
})
http.ListenAndServe(":8080", router)
The second one is simple too, we just move the authentication process to the Server.OnConnect
instead. You can get the original http.Request
by Conn.Socket().Request()
.
Remember, if a Server.OnConnect
event returns a non-nil error then the server immediately closes the connection before everything else. The client-side will receive this error through its client, err := neffos.Dial(...)
for Go client and catch
callback of the javascript's neffos.dial(...)
function(see below).
// [websocketServer := neffos.New...]
websocketServer.OnConnect = func(c *neffos.Conn) error {
r := c.Socket().Request()
if !isAuthenticated(r) {
// this error will return back to
// the client's `neffos.Dial` fucntion.
return errors.New("not authenticated")
}
// if succeed...
log.Printf("[%s] connected to the server", c.ID())
return nil
}
// [client, err := neffos.Dial...]
if err != nil {
// err.Error() == "not authenticated"
// [handle unauthenticated error...]
}
Finally, let's continue with a more real-world example using JWT and Iris. Iris offers a community-driven jwt middleware to use.
1. Install it by executing the following shell command:
$ go get github.com/iris-contrib/middleware/jwt
# and if you don't have Iris installed yet:
$ go get github.com/kataras/iris/v12@latest
2. This example extracts the token through a "token"
url parameter. Authenticated clients should be designed to set that with a signed token. This middleware has two methods, the first one is the Serve
method - it is an iris.Handler
and the second one is the CheckJWT(iris.Context) bool
.
import (
// [...]
"github.com/iris-contrib/middleware/jwt"
)
// [...]
j := jwt.New(jwt.Config{
// Extract by the "token" url.
Extractor: jwt.FromParameter("token"),
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte("My Secret"), nil
},
SigningMethod: jwt.SigningMethodHS256,
})
3. To register it before websocket upgrade process, you simply prepend the jwt j
middleware to the registered websocket endpoint route, before the websocket.Handler(websocketServer)
.
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/websocket"
"github.com/kataras/neffos"
"github.com/iris-contrib/middleware/jwt"
)
func main(){
app := iris.New()
// [websocketServer := neffos.New....]
app.Get("/echo", j.Serve, websocket.Handler(websocketServer))
app.Run(iris.Addr(":8080"))
}
3.1. To register it on an event you can use jwt middleware's CheckJWT(iris.Context) error
method and return any error to the event callback.
// [websocketServer := neffos.New....]
websocketServer.OnConnect = func(c *neffos.Conn) error {
ctx := websocket.GetContext(c)
if err := j.CheckJWT(ctx); err != nil {
// will send the above error on the client
// and will not allow it to connect
// to the websocket server at all.
return err
}
log.Printf("[%s] connected to the server", c.ID())
return nil
}
4. To get the claims/the payload inside a websocket event callback:
- when you have access to the jwt middleware you can just use its
Get
method.
ctx := websocket.GetContext(...)
user := j.Get(ctx)
- otherwise use the
ctx.Values().Get("jwt")
which is the key that the*jwt.Token
value is stored on authenticated requests.
func (nsConn *neffos.NSConn, msg neffos.Message) error {
ctx := websocket.GetContext(nsConn.Conn)
user := ctx.Values().Get("jwt").(*jwt.Token)
log.Printf("This is an authenticated request\n")
log.Printf("Claim content:")
log.Printf("%#+v\n", user.Claims)
// [...]
}
5. And the client-side, at this case a browser, might look something like that.
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIx"+
"MjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0Ijoz"+
"MjEzMjF9.8waEX7-vPKACa-Soi1pQvW3Rl8QY-SUFcHKTLZI4mvU";
var wsURL = "ws://localhost:8080/echo?token=" + token;
// [...]
try {
var conn = await neffos.dial(wsURL, { namespace: events...});
} catch(err) {
// [handle unauthenticated error...]
}
Of course in production, your server side must contain routes that their job will be to create the tokens for each of your users and then the client-side must store it and send it as HTTP Header on each request to the server, the default jwt's behavior to extract a token value is by the
Authentication: Bearer $TOKEN
header.
Home | About | Project | Getting Started | Technical Docs | Copyright © 2019-2023 Gerasimos Maropoulos. Documentation terms of use.