PC SOFT

FORUMS PROFESSIONNELS
WINDEVWEBDEV et WINDEV Mobile

Accueil → WINDEV 2024 → Authentification OAuth 2.0 d'application à serveur (sans popin d'autorisation)
Authentification OAuth 2.0 d'application à serveur (sans popin d'autorisation)
Débuté par Nico, 27 déc. 2016 18:31 - 13 réponses
Membre enregistré
122 messages
Popularité : +5 (5 votes)
Posté le 27 décembre 2016 - 18:31
J'essaye désespérément d'effectuer une Authentification OAuth 2.0 d'application Windev à serveur
Voir la page suivante : https://developers.google.com/identity/protocols/OAuth2ServiceAccount…

Cette authentification permet de connecter les utilisateurs d'une application Windev vers les services de Google sans popin d'Authentification (Par exemple pour effectuer des transferts vers Google Drive sans que l'utilisateur soit obligé de connaitre le login/pass du compte Google drive => G suite)

Mon problème est que Google me renvoi toujours le Jeton d'erreur suivant :
{
"error": "invalid_grant",
"error_description": "Invalid JWT Signature."
}

J'ai effectué plus d'une centaines de test et je sais que c'est la fonction HashChaîne qui ne me renvoi pas le bon résultat car si mes valeurs sJwtHeader ou sJwtClaim sont mauvaises, j'ai un autre message d'erreur.

Voici une partie du code

// Variable permettant de définir les valeur de "exp" et "iat" du jeton "claim"
duExp est une Durée
duIat est une Durée
dhRef est une DateHeure = "19700101000000"
dhMaintenant est une DateHeure = DateHeureLocaleVersUTC(DateSys()+HeureSys()) // pour le décalage horaire sinon erreur de durée renvoyée par Google

duIat = dhMaintenant - dhRef
duExp = duIat
duExp..Heure += 1 // Le jeton expire au bout d'une heure maximum (c'est la limite)

sJwtHeader est une chaîne ANSI = [
{"alg":"RS256","typ":"JWT"}
]

sJwtClaim est une chaîne ANSI = "{"+...
"""iss"":""xxxxxxxxx@xxxxxxxxx.iam.gserviceaccount.com"","+...
"""scope"":""https://www.googleapis.com/auth/drive"","+...
"""aud"":""https://www.googleapis.com/oauth2/v4/token"","+...
"""exp"":"+PartieEntière(duExp..EnSecondes)+","+...
"""iat"":"+PartieEntière(duIat..EnSecondes)+...
"}"

Base64Header est une chaîne ANSI = SansCaractèreDroite(Crypte(sJwtHeader, "", compresseAucun+crypteAucun, encodeBASE64), "=")
Base64Claim est une chaîne ANSI = SansCaractèreDroite(Crypte(sJwtClaim, "", compresseAucun+crypteAucun, encodeBASE64), "=")

// Je ne sais pas si cette clé privée est correctement structuré, j'ai remplacé les "\n" par des Caract(10) après plusieurs recherches...
private_key est une chaîne ANSI = ""+...
"-----BEGIN PRIVATE KEY-----"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"+Caract(10)+...
Caract(10)+"-----END PRIVATE KEY-----"+Caract(10)+...
""

bufHash est un Buffer = HashChaîne(HA_HMAC_SHA_256, Base64Header+"."+Base64Claim, private_key)

//J'obtient un buffer de 32 octets.

// Code que j'ai fini par commenter, je ne comprend pas si je dois utilisé quelque chose de ce genre...
//Hash est une chaine ANSI = ""
//
//tailleHash est un entier = Taille(bufHash)
//POUR i = 1 A tailleHash
// Hash+= NumériqueVersChaîne(Asc(bufHash[[i]]), "02x")
//FIN
//sSHA256 est une chaîne ANSI = SansCaractèreDroite(Crypte(Hash, "", crypteAucun+compresseAucun, encodeBASE64), "=")

sSHA256 est une chaîne ANSI = SansCaractèreDroite(Crypte(bufHash, "", crypteAucun+compresseAucun, encodeBASE64), "=")
sSHA256 = Remplace(sSHA256, "+", "-")
sSHA256 = Remplace(sSHA256, "/", "_")

maRequete est une HTTPRequête
maRequete..URL = "https://www.googleapis.com/oauth2/v4/token"
maRequete..Méthode = httpPost

HTTPCréeFormulaire("jeton")
HTTPAjouteParamètre("jeton", "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
HTTPAjouteParamètre("jeton", "assertion", Base64Header+"."+Base64Claim+"."+sSHA256)

cMaRéponse est un httpRéponse = HTTPEnvoieFormulaire("jeton", maRequete)

SI ErreurDétectée ALORS
Erreur(ErreurInfo(errComplet))
SINON
Info(cMaRéponse..Contenu)
FIN


C'est la que j'obtient dans la variable cMaRéponse..Contenu
{
"error": "invalid_grant",
"error_description": "Invalid JWT Signature."
}

Mon code peut paraître être du charabia, mais si quelqu'un s'est déjà penché sur ce type de problème, il comprendra mes difficultés ;)

Sinon j'espère que mon code pourra donner quelques pistes aux personnes qui débute avec Oauth2 de Google pour une authentification sans popin (avec popin c'est ce que font les nouvelles fonctionnalités de Windev 22). Et qu'ils puissent ensuite me donner des pistes ;)
Posté le 03 janvier 2017 - 17:03
Bonjour,
Nous allons être confronté à la même difficulté que vous dans quelques jours.
Dans l'entête, vous indiqué comme algo RS256 et ensuite vous crypté en SHA256.
Avez-vous essayé de passer en algo HS256 plutôt ?

En tout cas, si vous avez la solution, elle m'intéresse ...
Membre enregistré
122 messages
Popularité : +5 (5 votes)
Posté le 04 janvier 2017 - 08:39
Bonjour Steeve,

Dans la documentation de Google ils indiquent d'utiliser "RSA SHA-256" ce qui correspond pour eux à RS256 dans le fichier d'entête.
Pour le moment j'ai mis le projet en stand-by (ce n'était pas une priorité pour nous, il s'agissait plutôt de R&D avec les services Google pour en analyser les possibilités d'intégration dans une application).

Si vous arrivez à mettre en oeuvre la solution, je suis intéressé de savoir ce que j'avais fait de travers :p

Bonne journée :)
Membre enregistré
122 messages
Popularité : +5 (5 votes)
Posté le 12 mai 2017 - 14:45
Je viens de poursuivre mes tests que j'avais laissé de côté au mois de Décembre, et je n'arrive toujours pas à m'identifier sur les serveurs de Google avec Windev. J'y arrive parfaitement depuis PHP en portant mon code. Mais je pense que dans Windev la ligne

bufHash est un Buffer = HashChaîne(HA_HMAC_SHA_256, Base64Header+"."+Base64Claim, private_key)

N'est pas adapter ou fausse...

En PHP j'utilise

openssl_sign($signing_input, $Base64Header.".".Base64Claim, $private_key, OPENSSL_ALGO_SHA256);

Et ça fonctionne parfaitement

Quelque saurait quel est l'équivalent de la methode "openssl_sign" en Windev ou comment la porter ?

Bonne journée

--
Nicolas Gonot - 2exVia
Agence de communication multimédia depuis 1996
http://www.2exvia.com
Membre enregistré
122 messages
Popularité : +5 (5 votes)
Posté le 29 mai 2017 - 09:40
Pour ceux que ça intéresse, je vous invite à tester l'exemple "WD Oauth JWT", fournit dans le CD-ROM du TDF Tech 2017, pour faire de l'authentification serveur à serveur. L'obtention d'un jeton d'identification fonctionne parfaitement

--
Nicolas Gonot - 2exVia
Agence de communication multimédia depuis 1996
http://www.2exvia.com
Posté le 31 mai 2017 - 09:15
Identification Oauth2 ne pose heureusement pas trop de problèmes! mais pour ce qui est du cryptage entre windev, windev mobile et un site web là ... . Le cryptage "CrypteStandard" vous donnera beaucoup plaisir! Un truc pour un bizutage un lundi matin :merci: - openssl_sign($signing_input, $Base64Header.".".Base64Claim, $private_key, OPENSSL_ALGO_SHA256); -
Posté le 11 mars 2021 - 18:03
Bonjour ,

j'ai le même problème.
Avez-vous enfin trouvé la solution?
Si oui, pouvez-vous ajouter le code correcte?

merci beacoup
Kurt
Membre enregistré
122 messages
Popularité : +5 (5 votes)
Posté le 12 mars 2021 - 09:08
Je passe finalement par un webservice que j'ai écrit en PHP, Windev interroge et communique avec ce webservice

--
Nicolas Gonot - 2exVia
Agence de communication multimédia depuis 1996
http://www.2exvia.com
Posté le 12 mars 2021 - 09:13
ok, merci pour votre réponse.
Membre enregistré
16 messages
Posté le 22 février 2022 - 05:51
Bonjour,

voici un code qui permet de récupérer un token oAuth de l'API Microsoft Graph pour une utilisation de serveur à serveur, sans besoin d'authentification d'un utilisateur. Je me suis basé sur la doc Microsoft suivante : https://docs.microsoft.com/fr-fr/graph/auth-v2-service

Bon OK, ce n'est pas pour Google, mais ça peu aider pour ceux qui comme moi sont tombés sur ce post quand j'ai cherché une solution de serveur à serveur pour Microsoft. La solution d'utiliser l'exemple "WD Oauth JWT" proposée par Nico ci-dessus ne fonctionne plus à cause que Microsoft a supprimé l'authentification ACS en 2018.

//**********************************************************************************************
//Il faut commencer par ajouter une application dans le portail Microsoft Azur : //https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade

//Pour une connexion à l'API en tant qu'application (et pas en tant qu'utilisateur), il faut ajouter les autorisations (Contacts.readWrite,
//calendar.readWrite, etc.) d'une manière spécifique dans le portail Azur : au moment du choix de l'ajout de l'autorisation ("API autorisées" sur la
//gauche), il faut choisir "ajouter une autorisation" au dessus de la liste, choisir "Microsoft Graph", à droite, dans la demande "Quel type d'autorisation
//votre application nécessite-t-elle?" choisir "Autorisation de l'application", puis choisir les API à autoriser.
//ATTENTION : lorsque l'API a été ajoutée dans la liste, ne pas oublier de donner l'autorisation en tant qu'Administrateur en cliquant sur le bouton
//"Accorder un consentement en tant qu'Administrateur pour %tenant Microsoft%".
//*********************************************************************************************

//email de l'utilisateur qui sera utilisé par le token pour récupérer des infos via l'API graph
sEmailUser_quiDoitSeConnecterEnTantQuApplication est une chaine = "monUtilisateur@nomEntreprise.fr"

//ID d'application consultable dans "Vue d'ensemble" sur le portail Microsoft Azur
sO365_ClientID est une chaîne = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"

// Le Secret client doit être généré dans "Certificats et secrets" sur le portail Microsoft Azur
sO365_ClientSecret est une chaîne = "xxxxxxxxxxxxxxxxxxxxxxxxx"

//Date d'expiration du secret client => afin d'avertir l'administrateur à l'approche de l'expiration, mettre à jour cette date à chaque prolongation
dO365_ClientSecret_Expiration est une Date = "20240101"

//Nom du tenant ou l'application a été déclarée. Il est de la forme "monentreprise.onmicrosoft.com" ou "nomEntreprise.fr" si le domaine de l'entreprise est géré par Microsoft
sO365_Tenant est une chaîne = "nomEntreprise.fr"

//autorisation demandées, séparées par un espace
sScope est une chaîne = "https://graph.microsoft.com/.default" //ce scope ici défini est spécifique à la connexion "en tant qu'application". Pour une connexion en tant qu'utilisateur, le scope est habituellement de type "Calendars.ReadWrite Mail.ReadWrite Mail.Send Group.ReadWrite.All Contacts.ReadWrite Contacts.ReadWrite.Shared"


bOkDate est un booleen = VRAI
SI dO365_ClientSecret_Expiration < DateSys() ALORS
Erreur("Le ClientSercret Office 365 a expiré"+RC+"Veuillez le renouveler")
bOkDate = FAUX
SINON
dDatesysPlus3Mois est une Date = DateSys()
dDatesysPlus3Mois..Mois +=3
SI dO365_ClientSecret_Expiration < dDatesysPlus3Mois ALORS
Info("Le ClientSercret Office 365 va expirer le "+DateVersChaîne(dO365_ClientSecret_Expiration)+RC+"Veuillez le renouveler rapidement")
FIN
FIN

Si bOkDate ALORS
MonHttpRequ est un HTTPRequête

MonHttpRequ.Méthode = httpPost
MonHttpRequ..URL = "https://login.microsoftonline.com/[%sO365_Tenant%]/oauth2/v2.0/token"

sContenu est une chaîne = [
client=%1&client_id=%2&scope=%3&client_secret=%4&grant_type=client_credentials
]
sContenu = ChaîneConstruit(sContenu,sEmailUser_quiDoitSeConnecterEnTantQuApplication,sO365_ClientID,sScope,sO365_ClientSecret)
MonHttpRequ..Contenu = sContenu

HttpMaReponse est un httpRéponse = HTTPEnvoie(MonHttpRequ)

// Récupération du token
SI HttpMaReponse.CodeEtat = 200 ALORS
// Déclare les paramètres, nécessaire pour le rafraîchissement du token
oAuth2Param est un OAuth2Paramètres
oAuth2Param.ClientID = sO365_ClientID
oAuth2Param.ClientSecret = sO365_ClientSecret
oAuth2Param.URLAuth = "https://login.microsoftonline.com/[%sO365_Tenant%]/oauth2/v2.0/token" //pas sûr de ce qui doit être mis
oAuth2Param.Scope = "https://graph.microsoft.com/.default"
oAuth2Param.URLToken = "https://login.microsoftonline.com/[%sO365_Tenant%]/oauth2/v2.0/token" //pas sûr de ce qui doit être mis

// Initialise le token avec le JSON
MonToken est un AuthToken(oAuth2Param, HttpMaReponse.Contenu)
gnMontoken <= MonToken
FIN

SI gnMontoken = Null ALORS
Erreur("La connexion à Office 365 a échouée"+RC+"Token null")
RENVOYER FAUX
SINON
SI gnMontoken.Valide = FAUX ALORS
Erreur("La connexion à Office 365 a échouée"+RC+gnMontoken.RéponseServeur)
RENVOYER FAUX
SINON

Info("token valide jusqu'au " + DateHeureVersChaîne(gnMontoken.DateExpiration))

RENVOYER VRAI

FIN
FIN
SINON
RENVOYER FAUX
FIN


Une fois le token récupéré, on peut l'utiliser de la même manière que si un utilisateur s'était connecté, bien qu'il y ai quelques différences sur les autorisations disponibles (voir le site de Microsoft : https://docs.microsoft.com/fr-fr/graph/auth-v2-service )
Message modifié, 22 février 2022 - 06:38
Membre enregistré
16 messages
Posté le 22 février 2022 - 06:10
A noter une différence lorsqu'on utilise un token en tant qu'application pour récupérer des infos sur un utilisateur : l'URL d'appel dans la requête HTTP.

Voici un exemple : pour récupérer les contacts d'un utilisateur avec un token récupéré "en tant qu'application", l'URL d'appel doit être comme ceci :
cMyRequestHTTPsend..URL = "https://graph.microsoft.com/v1.0/users/"+sEmailUser+"/contacts"


alors qu'un appel avec un token en tant qu'utilisateur doit être ceci :
cMyRequestHTTPsend..URL = "https://graph.microsoft.com/v1.0/me/contacts"
Posté le 30 août 2022 - 16:37
Bonjour,
Quentin, grâce à ton code et en utilisant mes paramètres j'obtient bien mon Token.
Par contre, lorsque je l'utilise pour envoyer ou recevoir des mails, j'ai à chaque fois une erreur :
En réception :
Session IMAP, accès refusé
La dernière réponse du serveur IMAP est :
04 NO LOGIN failed

Session IMAP, accès refusé
La dernière réponse du serveur IMAP est :
03 NO AUTHENTICATE failed

et en envoi :
Session SMTP, accès refusé

Je suis sur de mes login et mot de passe

Je câle . . .

Une aide serait la bienvenue

Cordialement
Posté le 28 novembre 2022 - 16:17
Bonjour à tous,
Même problème ici, récupération sans soucis du token "application" mais message d'erreur pour exploiter l'ouverture d'une session imap ou pour envoyer un mail smtp ....
Avez vous trouvé la solution ?
Cordialement
Posté le 02 février 2024 - 14:29
Bonjour,

Je me permets de relancer le sujet. J'ai le même besoin pour utiliser l'API de Docusign avec Webdev 27.

Malheureusement, le composant Docusign (version 2024) fonctionne mais avec une authentification : Authorization Code Grant -> à chaque appel pour faire signer un document, il faut que l'utilisateur soit connecté. Si ce n'est pas le cas, un login / mdp lui ait demandé (identification Docusign)

J'aimerai donc utiliser l’authentification : JWT Grant -> celle ci permet d'envoyer des documents à Docusign sans s'identifier sur Docusign (donc transparent pour l'utilisateur final).

Comme j'ai compris, voici les étapes à suivre :

1) Créer un compte développeur sur Docusign -> OK
2) Créer l'application dans "APP and Keys" -> OK
3) Demande d'autorisation de demande -> OK

4) Créer un JWT -> là, ça se complique, j'ai tenté plusieurs choses mais ça ne fonctionne pas -> Signature invalide quand test sur le site https://jwt.io/

5) Obtenir le jeton d'accès -> A faire une fois l'étape précédente OK

Je pense que le problème vient du crytage de la signature... Quelqu'un a déjà fait cela ?

Merci d'avance