Patrice Ferlet
Patrice Ferlet
Créateur de ce blog.
Publié le févr. 28, 2016 Temps de lecture: 5 min

Me suivre sur Mastodon

JWT en Go

Vous avez envie de gérer l’autorisation de vos API REST en Go avec JWT ? Personnellement c’est mon cas. Alors voilà comment on s’y prend.

JWT (JSON Web Token) est fortement utilisé depuis plusieurs années maintenant. Il permet de signer une clef et de permettre l’autorisation d’un utilisateur dans des échanges client-serveur. Il est intégré au protocole OAuth2 et bénéficie d’une RFC.

Pour faire simple, c’est un peu comme gérer des sessions… Mais sans session.

Hein ? Keskidi ?

– un lecteur bourré

Bon pour faire simple, sans aller dans le détail de OAuth2 que nous n’allons pas utiliser, voilà en gros le déroulé:

  • un utilisateur se connecte sur le serveur en envoyant un login et mot de passe
  • le serveur authentifie (ou non) l’utilisateur
  • le serveur crée alors un “token” signé et crypté qu’il retourne à l’utilisateur

Et après cela, à chaque requête de l’utilisateur, le client doit envoyer ce token pour être reconnu par le serveur.

Mais comment le serveur sait que ce token est bien au format attendu ? C’est ultra simple ! Le token est en fait une combinaison d’information dont un JSON qui contient des informations reconnues par le serveur (et lui seul, à moins de donner la clef secrète à d’autres).

Donc, quand le serveur reçoit une requête, il décrypte le token avec une clef secrète (qui a servi à encrypter le token) et il vérifie des informations. Dans ces informations il y a généralement au moins une date d’expiration. Si cette date est dépassée, le client va devoir redemander un token.

Bref, c’est assez bien fichu. C’est pas non plus infaillible (il parait), mais ça a le mérite de faire le job attendu, et ça permet surtout d’éviter au serveur de retenir des sessions. Du coup, on peut utiliser le même token sur une application “scallée”, à condition de partager la clef avec tous les noeuds afin qu’ils puissent tous authentifier l’utilisateur.

En visitant la page https://jwt.io/ j’ai découvert que le package “jwt-go” proposé est celui-ci: https://github.com/dgrijalva/jwt-go

Voyons donc comment il marche.

Exemple Simple en Go

On va juste faire un petit test, on commence par installer le package:

go get -u github.com/dgrijalva/jwt-go

Et on se fait un petit programme pour voir. On crée un répetoire “~/jwt-test” et on crée le fichier “~/jwt-test/main.go”:

package main

import (
    "log"
    "time"
    "github.com/dgrijalva/jwt-go"
)

var SECRET = []byte("Une phrase longue pour encrypter/décrypter le token")

func main(){
    token := jwt.New(jwt.SigningMethodHS256)
    // dans Claims, on peut mettre ce qu'on veut
    token.Claims["id"] = 123456

    // la clef "exp" est là par défaut, on peut la modifier
    // c'est la date d'expiration du token
    token.Claims["exp"] = time.Now().Add(time.Hour * 24)

    // on récupère la chaine
    jwtstring, err := token.SignedString(SECRET)
    if err != nil {
        log.Fatal(err)
    }
    // voilà la clef
    log.Println(jwtstring)
}

Cette clef peut donc être retournée au client. Il va la garder dans un coin et l’envoyer dans une entête ou en argument pour faire des requêtes. Le serveur doit donc maintenant être capable de décrypter le token. Voilà comment faire:

// je vous laisse faire la fonction "getHeader"
jwtstring := getHeader("Authorization")

// on décrypte
// je vais revenir sur la fonction utilisée pour récupérer le secret
token, err := jwt.Parse(jwtstring, func(token *jwt.Token)(interface{}, error){
    return SECRET, nil
})

if !token.Valid {
    log.Fatal("Et bha non...", err)
}

log.Println("id utilisateur: ", token.Claims["id"])

A quoi sert la fonction utilisée dans “Parse()” ? C’est assez simple, vous pouvez créer une fonction qui, selon les entêtes, la requête, le type d’utilisateur, retournera une clef spécifique. C’est loin d’être idiot. Mais dans notre cas nous n’allons pas faire compliqué et retourner la variable “SECRET”.

Le miracle opère, vous avez l’id utilisateur. Puisque le token est valide, c’est à dire que le serveur a réussi à le décrypter et que la date “exp” n’est pas dépassée, et bien vous pouvez être certain que l’utilisateur est le bon.

À vous de choisir quoi mettre dans le token, ici nous avons intégré l’identifiant du compte, mais vous pouvez ajouter son email, son login, sa pointure de chaussure… Bref, ce qui vous intéresse. Certains s’amusent à foutre tout l’objet utilisateur à l’intérieur. C’est bourri hein…

J’aime les auteurs de package !

Franchement, en Go, je ne sais pas pourquoi, mais je trouve que les auteurs de package s’arrachent vraiment pour rendre la vie facile aux développeurs. Là, par exemple, l’auteur du package jwt-go a pensé à mettre une méthode “ParseFromRequest” qui fonctionne super bien.

Plutôt que de chercher vous-même l’entête ou la variable postée par le client, la fonction le fait pour vous:

// une méthode qui demande une authentification
func GetSomething(w http.ResponseWriter, req *http.Request){

    // on décrypte
    token, err := jwt.ParseFromRequest(req, func(token *jwt.Token)(interface{}, error){
        return SECRET, nil
    })

    if !token.Valid {
        http.Error(w, "Problème avec ton token", http.StatusUnauthorized);
        return
    }

    w.Write([]byte("Yes !"))
}

Pour faire simple, la méthode cherche l’entête “Authorization” ou la valeur POST “access_token”. Vous voyez, c’est pas compliqué.

Conclusion

JWT est une notion essentielle de nos jours, il faut dire que les API REST sont à la mode et que la scallabilité n’aide pas la gestion de session. Donc, JWT est une bonne réponse à cette problématique et elle a le mérite d’être assez sûre.

En Go, c’est finalement pas si compliqué.

comments powered by Disqus