Authenticating to Azure in Go using REST API - azure

I am trying to authenticate to Azure service management / graph API using golang.
Using purely REST APIs.
No matter what I do, I always end up with error:
{"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter: 'grant_type'.
Since I am not using SDK there is limited samples out there. Any help would be appreciated.
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
func main() {
authendpoint := "https://login.microsoftonline.com/8xxxxx7-6372-4bcb-xxx-xxxxxx/oauth2/token"
jsonData := []byte(`{
"resource": "https://graph.microsoft.com",
"client_id": "xxxxxxxx-7549-4ea2-b00d-xxxxxxxxxxx",
"client_secret": "Q.xxxxxxxxxxxxxx-6_CgA4yOi_8sS-",
"grant_type": "client_credentials",
}`)
request, err := http.NewRequest("POST", authendpoint, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
var res map[string]interface{}
json.NewDecoder(resp.Body).Decode(&res)
log.Println(string(body))
}

The Microsoft request docs posted by Praveen Premaratne show the request needs to be formatted using Content-Type: application/x-www-form-urlencoded which is a requirement for the OAuth 2.0 standard.
Here's the Microsoft docs and example:
https://learn.microsoft.com/en-us/graph/auth/auth-concepts#register-your-app-with-the-microsoft-identity-platform
POST /common/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20mail.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=JqQX2PNo9bpM0uEihUPzyrh
Here's how to accomplish this:
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
authendpoint := "https://login.microsoftonline.com/8xxxxx7-6372-4bcb-xxx-xxxxxx/oauth2/token"
body := url.Values(map[string][]string{
"resource": {"https://graph.microsoft.com"},
"client_id": {"xxxxxxxx-7549-4ea2-b00d-xxxxxxxxxxx"},
"client_secret": {"Q.xxxxxxxxxxxxxx-6_CgA4yOi_8sS-"},
"grant_type": {"client_credentials"}})
request, err := http.NewRequest(
http.MethodPost,
authendpoint,
strings.NewReader(body.Encode()))
if err != nil {
panic(err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
fmt.Println(resp.StatusCode)
}

for me, it worked when I removed resource from the request body and added scope as new parameter in body.

Related

How to access already authenticated user from web application behind Google Identity Aware Proxy?

I have a web application which sits behind Google's Identity Aware Proxy (IAP). IAP authenticates the user before forwarding to my web application. How can I access the already authenticated user from my web application?
In Getting the user's identity it states there are X-Goog-Authenticated-User-Email and X-Goog-Authenticated-User-Id headers. However, I don't see those in the response headers:
accept-ranges: bytes
alt-svc: clear
content-length: 14961
content-type: text/html; charset=utf-8
date: Thu, 01 Apr 2021 15:21:01 GMT
last-modified: Wed, 31 Mar 2021 19:34:58 GMT
via: 1.1 google
I do see a few cookies:
GCP_IAAP_AUTH_TOKEN_xxx
GCP_IAP_UID
GCP_IAP_XSRF_NONCE_xxx
For example, I want to be able to show their name and avatar photo in my web app to show that they are authenticated and logged in. I know that info is available via Google's OAuth2 struct, but how can I get that from IAP?
I was able to get this working after #JohnHanley mentioned that the headers only show up when running behind IAP. You cannot see them during local development.
I could see them after deploying a simple, temporary, /headers route which loops through them and writes to the ResponseWriter. X-Goog-Authenticated-User-Id, X-Goog-Authenticated-User-Email and X-Goog-Iap-Jwt-Assertion.
import (
"fmt"
"net/http"
"github.com/rs/zerolog/log"
)
func headersHandler(w http.ResponseWriter, r *http.Request) {
log.Info().Msg("Entering headersHandler")
fmt.Fprintf(w, "Request Headers\n\n")
log.Debug().Msg("Request Headers:")
for name, values := range r.Header {
log.Debug().Interface(name, values).Send()
fmt.Fprintf(w, "%s = %s\n", name, values)
}
}
This was a temporary route. Once I could confirm the headers, I deleted it.
Additionally, I had to enable Google's People API for the ProjectId where my web application was being hosted.
Afterwards, I did a test using the Go package for google.golang.org/api/people/v1 and found that the convention of using the currently authenticated user via people/me didn't work in my case since it returns the service account being used. Instead, I had to programmatically fill in the user id people/userid. Then it worked.
For my use-case, I created a /user route to return a subset of the user information, i.e. name, email, photo url.
Struct:
type GoogleUser struct {
Name string `json:"name"`
Email string `json:"email"`
PhotoUrl string `json:"photo_url"`
}
Handler:
func userHandler(w http.ResponseWriter, r *http.Request) {
log.Info().Msg("Entering userHandler")
var err error
// Make sure this is a valid API request
// Request header Content-Type: application/json must be present
if !ValidAPIRequest(r) {
err = writeJSONError(w, ResponseStatusNotFound("Not found"))
if err != nil {
log.Error().Msg(err.Error())
}
return
}
// Extract user id from header
var userId string = r.Header.Get("X-Goog-Authenticated-User-Id")
if userId != "" {
userId = strings.ReplaceAll(userId, "accounts.google.com:", "")
}
// Extract user email from header
var userEmail string = r.Header.Get("X-Goog-Authenticated-User-Email")
if userEmail != "" {
userEmail = strings.ReplaceAll(userEmail, "accounts.google.com:", "")
}
// Get the currently authenticated Google user
googleUser, err := GetCurrentGoogleUser(userId, userEmail)
if err != nil {
log.Error().Msg(err.Error())
err = writeJSONError(w, ResponseStatusInternalError(err.Error()))
if err != nil {
log.Error().Msg(err.Error())
}
return
}
// Write the JSON response
err = writeJSONGoogleUser(w, http.StatusOK, &googleUser)
if err != nil {
log.Error().Msg(err.Error())
}
}
Google People API:
func GetCurrentGoogleUser(userId string, userEmail string) (GoogleUser, error) {
// Pre-conditions
if userId == "" {
return GoogleUser{}, errors.New("userId is blank")
}
if userEmail == "" {
return GoogleUser{}, errors.New("userEmail is blank")
}
log.Debug().
Str("userId", userId).
Str("userEmail", userEmail).
Send()
ctx := context.Background()
// Instantiate a new People service
peopleService, err := people.NewService(ctx, option.WithAPIKey(GoogleAPIKey))
if err != nil {
return GoogleUser{}, err
}
// Define the resource name using the user id
var resourceName string = fmt.Sprintf("people/%s", userId)
// Get the user profile
profile, err := peopleService.People.Get(resourceName).PersonFields("names,photos").Do()
if err != nil {
return GoogleUser{}, err
}
log.Debug().
Interface("profile", profile).
Send()
return GoogleUser{Name: profile.Names[0].DisplayName, Email: userEmail, PhotoUrl: profile.Photos[0].Url}, nil
}

Upload files to Azure Storage from Azure VM using SDK azblob and Managed Service Identity

I am trying to upload files to azure storage container using Go SDK for Azure storage from an Azure VM which has Azure Managed Identity attached to it. I am also using Azure auth to create a ServicePrincipalToken using MSIConfig. However I am receiving an error
RESPONSE Status: 400 Authentication information is not given in the correct format. Check the value of Authorization header.
Can someone please help me understand what I am missing?
Script I have used (modified form of the example ):
// main.go
package main
import (
"log"
"fmt"
"context"
"net/url"
"strings"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/Azure/go-autorest/autorest/azure/auth"
)
func main() {
azureServicePrincipalToken, err := auth.NewMSIConfig().ServicePrincipalToken()
if err != nil {
log.Fatal(err)
}
accountName := "<TESTSA>"
containerName := "<TESTCONTAINER>"
// Create a BlockBlobURL object to a blob in the container (we assume the container already exists).
u, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s/readme.txt", accountName, containerName))
credential := azblob.NewTokenCredential(azureServicePrincipalToken.Token().AccessToken, nil)
if err != nil {
log.Fatal(err)
}
blockBlobURL := azblob.NewBlockBlobURL(*u, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
log.Println(blockBlobURL)
ctx := context.Background() // This example uses a never-expiring context
// Perform UploadStreamToBlockBlob
bufferSize := 2 * 1024 * 1024
maxBuffers := 3
_, err = azblob.UploadStreamToBlockBlob(ctx, strings.NewReader("Hello azblob"), blockBlobURL,
azblob.UploadStreamToBlockBlobOptions{BufferSize: bufferSize, MaxBuffers: maxBuffers})
if err != nil {
log.Fatal(err)
}
}
When I execute go run main.go, I receive the following error:
2020/12/26 17:58:07 https://<TESTSA>.blob.core.windows.net/<TESTCONTAINER>/readme.txt
2020/12/26 17:58:07 write error: -> github.com/Azure/azure-storage-blob-go/azblob.newStorageError, /home/<MYUSER>/go/pkg/mod/github.com/!azure/azure-storage-blob-go#v0.12.0/azblob/zc_storage_error.go:42
===== RESPONSE ERROR (ServiceCode=) =====
Description=Authentication information is not given in the correct format. Check the value of Authorization header.
RequestId:f30c063e-901e-0046-2cb0-db4781000000
Time:2020-12-26T17:58:07.7810745Z, Details:
Code: InvalidAuthenticationInfo
PUT https://<TESTSA>.blob.core.windows.net/<TESTCONTAINER>/readme.txt?blockid=j%2BItsAdqRN6EScZ3S2r8QwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D%3D&comp=block&timeout=61
Authorization: REDACTED
Content-Length: [12]
User-Agent: [Azure-Storage/0.12 (go1.13.9; linux)]
X-Ms-Client-Request-Id: [21638ec4-138c-434d-4b53-d13924e51966]
X-Ms-Version: [2019-12-12]
--------------------------------------------------------------------------------
RESPONSE Status: 400 Authentication information is not given in the correct format. Check the value of Authorization header.
Content-Length: [298]
Content-Type: [application/xml]
Date: [Sat, 26 Dec 2020 17:58:07 GMT]
Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
X-Ms-Request-Id: [f30c063e-901e-0046-2cb0-db4781000000]
exit status 1
I have also verified with the azcli command and I was able to upload a sample txt file helloworld to the storage container without any challenge. The commands I used:
az login --identity
az storage blob upload --container-name <TESTCONTAINER> --account-name <TESTSA> --name helloworld --file helloworld --auth-mode login
Response:
Finished[#############################################################] 100.0000%
{
"etag": "\"0x8D8A9CCDD921BA7\"",
"lastModified": "2020-12-26T18:34:22+00:00"
}
Thank you.
The code sample you refer to authorizes with Shared Key and Put Blob API, but not Azure AD.
credential, err := NewSharedKeyCredential(accountName, accountKey)
If you would like to authorize with Azure AD by ServicePrincipalToken, see Azure Active Directory authentication for Go.
applicationSecret := "APPLICATION_SECRET"
spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
appliationID,
applicationSecret,
resource,
callbacks...)
if err != nil {
return nil, err
}
// Acquire a new access token
err = spt.Refresh()
if (err == nil) {
token := spt.Token
}

Get Access Token Azure AD using client certificate with Golang

I am trying to do the modern authentication from azure ad with client certificate.
The process says.
Create an azure application.
Upload the certificate to azure application and get the thumbprint.
Generate the JWT token using that certificate(https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials)
Then use that jwt-token to get the access-token.
To get the access-token, call an API (https://login.microsoftonline.com//oauth2/token)
The HTTP method is of type POST and post body data will be x-www-form-urlencoded
After doing all the steps when i try to get the token.
I get this error.
{
"error": "invalid_client",
"error_description": "AADSTS700023: Client assertion audience claim does not match Realm issuer. Review the documentation at https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials .\r\nTrace ID: 714b5009-b74d-46b5-bd0e-3bea76272a01\r\nCorrelation ID: 2cb703bb-f325-44b5-a669-605bc7a81ac0\r\nTimestamp: 2020-08-28 07:31:19Z",
"error_codes": [
700023
],
"timestamp": "2020-08-28 07:31:19Z",
"trace_id": "714b5009-b74d-46b5-bd0e-3bea76272a01",
"correlation_id": "2cb703bb-f325-44b5-a669-605bc7a81ac0"
}
I am generating the JWT token using go code and parsing the PFX file.
func getAuthJWTToken() (string, error) {
clientID := "**********************"
_tenantName := "******************"
pfxFilePath := `E:\abcd.pfx`
certPassword := `*********`
authToken := ""
pfxFile, err := os.Open(pfxFilePath)
if err != nil {
return authToken, err
}
pfxfileinfo, _ := pfxFile.Stat()
var size int64 = pfxfileinfo.Size()
pfxbytes := make([]byte, size)
buffer := bufio.NewReader(pfxFile)
_, err = buffer.Read(pfxbytes)
//PFX to PEM for computation of signature
var pembytes []byte
blocks, err := pkcs12.ToPEM(pfxbytes, certPassword)
for _, b := range blocks {
pembytes = append(pembytes, pem.EncodeToMemory(b)...)
}
//Decoding the certificate contents from pfxbytes
pk, cert, err := pkcs12.Decode(pfxbytes, certPassword)
if cert == nil {
fmt.Printf("Bye")
return authToken, nil
}
if pk == nil {
}
pfxFile.Close() // close file
notToBeUsedBefore := time.Now()
expirationTime := time.Now().Add(3000 * time.Minute)
URL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", _tenantName)
id := guid.New()
claims := &claims{
StandardClaims: jwt.StandardClaims{
// In JWT, the expiry time is expressed as unix milliseconds
ExpiresAt: expirationTime.Unix(),
Audience: URL,
Issuer: clientID, // consumer key of the connected app, hardcoded
NotBefore: notToBeUsedBefore.Unix(),
Subject: clientID,
Id: id.String(),
},
}
//token_header map[string]interface{}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
sha1Fingerprint := sha1.Sum(cert.Raw)
var slice []byte
slice = sha1Fingerprint[:]
b64FingerPrint := base64.StdEncoding.EncodeToString([]byte(slice))
token.Header["x5t"] = b64FingerPrint
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(pembytes) // parse the RSA key
tokenString, err := token.SignedString(signKey) // sign the claims with private key
fmt.Printf(fmt.Sprintf("JWT token is %s", tokenString))
return tokenString, err
}
I need the help to resolve this issue.

How to get authenticate with the squareup using backend golang?

In sqaureup application Aplication_name in oauth option there is a redirect url Which will redirect a given url with the QueryString code. While I'm hitting https://connect.squareup.com/oauth2/authorize?client_id=YOUR_CLIENT_ID this url in the browser then it will redirect me to a given url in oauth with attached code. And then to take a access_token you have to give a POST request to given url https://connect.squareup.com/oauth2/token with the body
{
"client_id": "YOUR_APPLICATION_ID",
"client_secret": "YOUR_APPLICATION_SECRET",
"code": "M-Q7k-N0Emx_3cBqwbVLTQ",
"redirect_uri": "YOUR_REDIRECT_URI"
}
I do it same and send By method POST to this url with json data but it will gives me the error:-
{
"message": "Not Authorized",
"type": "service.not_authorized"
}
The Golang Code I'm using for this is :-
func Token(c *gin.Context) {
code := c.Query("code") // code be something like:-sq0cgp-wLVQt5HOLfug6xiVdmCDCf
splitCode := strings.Split(code, "-")
token := models.PostToken{
ClientID: "YOUR_APPLICATION_ID",
ClientSecret: "YOUR_APPLICATION_SECRET",
Code: splitCode[1],
RedirectUri: c.Request.Host + c.Request.URL.RequestURI(),
}
bindData, err := json.Marshal(token)
if err != nil {
panic(err)
}
var jsonStr = []byte(string(bindData))
url := "https://connect.squareup.com/oauth2/token"
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
fmt.Println(req, err)
}
Models struct:-
type PostToken struct {
ClientID string `json:"client_id" bson:"client_id"`
ClientSecret string `json:"client_secret" bson:"client_secret"`
Code string `json:"code" bson:"code"`
RedirectUri string `json:"redirect_uri" bson:"redirect_uri"`
}
You have to do some points I mentioned:-
First check your application Id.
In second parameter ClientSecret you have to use Oauth Application Secret key Which you will got from the application dashboard -> Oauth option.
In Code you don't have send the split code send simple string code value which your getting in the variable name code.
Fourth parameter is optional as the documentation says here.
Then you will got what you want :D.

How to pass the header in POST method?

Here I have a function in which I'm sending a POST request which is used to save customer in the squareup with data and also set the headers with Authentication using variable_name.Header.Set()
But in body response it will always give me error of the:-
"errors":[
{"category":"AUTHENTICATION_ERROR",
"code":"UNAUTHORIZED",
"detail":"Your request did not include an `Authorization` http header with an access token. }]}
But In the function I'm setting the authentication token.
Code:-
func CreateCustomer(c *gin.Context) {
customer := models.Customer{}
bearer := strings.Split(c.Request.Header["Authorization"][0], "Bearer")// token pass in the postman.
bearerToken := strings.TrimSpace(bearer[1])
customerErr := json.NewDecoder(c.Request.Body).Decode(&customer)
if customerErr != nil {
fmt.Println(customerErr)
return
}
fmt.Println(customer)
bindData, err := json.Marshal(customer)
if err != nil {
panic(err)
}
var jsonStr = []byte(string(bindData))
url :="https://connect.squareup.com/v2/customers"
fmt.Println(url)
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
// I used this one too.
// req.Header.Set("Authorization", "Bearer "+bearerToken)
// req.Header.Set("Accept", "application/json")
req.Header.Add("Authorization", "Bearer "+bearerToken)
req.Header.Add("Accept", "application/json")
fmt.Println(req.Header)
if err != nil {
panic(err)
}
defer req.Body.Close()
body, _ := ioutil.ReadAll(req.Body)
fmt.Println("response Body:", string(body))
}
type Customer struct {
GivenName string `json:"given_name" bson:"given_name"`
FamilyName string `json:"family_name" bson:"family_name"`
CompanyName string `json:"company_name" bson:"company_name"`
Nickname string `json:"nickname" bson:"nickname"`
EmailAddress string `json:"email_address" bson:"email_address"`
Address Addresss `json:"address" bson:"address"`
PhoneNumber string `json:"phone_number" bson:"phone_number"`
ReferenceId string `json:"reference_id" bson:"reference_id"`
Note string `json:"note" bson:"note"`
}
The req.Header result is:-
map[X-Xss-Protection:[1; mode=block]
Keep-Alive:[timeout=60]
Accept:[application/json]
X-Permitted-Cross-Domain-Policies:[none]
Content-Type:[application/json]
Vary:[Origin, Accept-Encoding]
X-Content-Type-Options:[nosniff]
X-Download-Options:[noopen]
X-Frame-Options:[SAMEORIGIN]
Date:[Wed, 12 Dec 2018 03:41:16 GMT]
Strict-Transport-Security:[max-age=631152000]
Authorization:[Bearer YOUR_TOKEN HERE]]
Can anyone tell me that what error should I'm doing or where I do correction that it will able to save customer in the Squareup?
Your code sends POST request and after request is processed it adds headers to response struct:
response, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
You should set headers first and send request after that:
// create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
// set headers
req.Header.Add("Authorization", "Bearer "+bearerToken)
req.Header.Add("Accept", "application/json")
// send request with headers
client := &http.Client{}
response, err := client.Do(req)
#Dmitry Harnitski this is the correct approach. Only reminder is that, try not confused between "authentication" and "authorization", which was kind of roadblock I encountered. It's not about literally meaning, everyone knows the difference. However, even in the syntax, a lot material used these two words interchangeably, which should not be encouraged, at least to my mind. The topic above is a good example: began with "authentication" as the question, then ended with "authorization" as the solution.

Resources