I'm trying to validate the Connect2id set up with Go test and I'm getting following error.
"Client authentication failed: Missing client authentication","error":"invalid_client"
The full scenario output is look like below.
Feature: Test Identity Provider
Scenario: # features/idp.feature:3
Given identity provider 'hf1-idp.json' # main_test.go:72 -> *Scaffold
When I request an access token as 'cc_test' # main_test.go:83 -> *Scaffold
oauth2: cannot fetch token: 401 Unauthorized
Response: {"error_description":"Client authentication failed: Missing client authentication","error":"invalid_client"}
Then the token should have a claim 'scope' # main_test.go:92 -> *Scaffold
And the token should have a claim 'sub' with value 'dips-mra' # main_test.go:106 -> *Scaffold
And the token should have a claim 'hso:userid' with value 'dips-mra' # main_test.go:106 -> *Scaffold
My hf1-idp.json file look like below.
{
"kind": "PING",
"issuer": "https://my.issuer.com/c2id",
"insecure": true,
"clients": {
"cc_test": {
"flow": "clientcredentials",
"id": "clientId",
"secret": "",
"scopes": ["openid", "solr"],
"values": {
"resource": ["https://my.solrnode1.com/solr/", "https://my.solrnode2.com/solr/"]
}
},
Connect2id works fine in the set up environment. As an example I get expected result when I run following Curl command with correct values
curl -k -s -H "Content-Type: application/json" -XPOST https://my.issuer.com/c2id/direct-authz/rest/v2 \
-H "Authorization: Bearer ztucBearerToken" \
-d '{
"sub_session" : { "sub" : "alice" },
"client_id" : "clientId",
"scope" : [ "openid", "solr" ],
"claims" : [ "name", "email", "email_verified", "access_token:hso:subject:system", "access_token:hso:subject:id", "access_token:hso:subject:name", "access_token:hso:subject:role:system", "access_token:hso:subject:role:id", "access_token:hso:subject:role:name", "access_token:hso:subject:organization:system", "access_token:hso:subject:organization:id", "access_token:hso:subject:organization:name", "access_token:hso:subject:organization:child-organization:system", "access_token:hso:subject:organization:child-organization:id", "access_token:hso:subject:organization:child-organization:name", "access_token:hso:purpose:system", "access_token:hso:purpose:id", "access_token:hso:purpose:description", "access_token:hso:resource:system", "access_token:hso:resource:id" ]
}'
Updated with following codes
main_test.go
func (sc *Scaffold) readIdentityProvider(filename string) error {
idp, err := idp.ReadIdentityProvider(context.Background(), "testdata/"+filename)
// More code goes here
}
provider.go
func ReadIdentityProvider(ctx context.Context, filename string) (*IdentityProvider, error) {
config, err := readIdentityProvider(filename)
if err != nil {
return nil, err
}
return NewIdentityProvider(ctx, config)
}
func NewIdentityProvider(ctx context.Context, config *Config) (*IdentityProvider, error) {
ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.Insecure,
},
},
})
provider, err := oidc.NewProvider(ctx, config.Issuer)
// More code goes here
}
oidc.go
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + "/direct-authz/rest/v2"
req, err := http.NewRequest("POST", wellKnown, nil)
if err != nil {
return nil, err
}
resp, err := doRequest(ctx, req) // Herer I get 401 Unauthorized
if err != nil {
return nil, err
}
// More code goes here
}
You are missing the Bearer token in the request. (https://connect2id.com/products/server/docs/integration/direct-authz#error-401)
When you curl you use the -H parameter to add the Authorization Bearer token
-H "Authorization: Bearer ztucBearerToken"
You need to do the same in your Go application.
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + "/direct-authz/rest/v2"
req, err := http.NewRequest("POST", wellKnown, nil)
bearer := "Bearer ztucBearerToken"
req.Header.Add("Authorization", bearer)
if err != nil {
return nil, err
}
resp, err := doRequest(ctx, req) // Herer I get 401 Unauthorized
if err != nil {
return nil, err
}
// More code goes here
}
Some supportive facts
Similar SO discussion goes here.
To get rid of the
400 Bad Request: {"error_description":"Bad request: Invalid JSON:
Unexpected token at position 0.","error":"invalid_request"}
what you have to do is pass necessary request body like below.
req, err := http.NewRequest("POST", wellKnown, bytes.NewBuffer(bytesRepresentation))
Fore more info visit, Golang documentation related to net/http
Related
We are using Golang to communicate with an external service using HTTPS. When attempting the request using cURL, we get success. When using Go, the certificate appears to be ignored, yielding a 403 from the external service.
We have been unable to spot any differences in the cURL-request vs the Golang code. Can somebody help us find a difference?
The cURL-request gives us a proper JSON response. The Go code gives:
2020/09/07 15:05:57 request error: perform request: api response: 403 Forbidden
Working cURL-request (user agent for debug purposes):
curl -X GET --http1.1 -i -v --key client.key.pem --cacert ca.pem --cert client.pem "https://[redacted]/path/to/endpoint" -H "Accept: application/json; charset=utf-8" -H "User-Agent: Apache-HttpClient/4.5.5 (Java/12.0.1)" -H "X-Identifier: [redacted]" -H "Accept-Encoding: gzip, deflate" -H "Connection: Keep-Alive"
Golang code yielding a 403:
(note: files ca.pem, client.pem (cert) and client.key.pem must be in same directory. run script as go run catest.go --url "https://[redacted]/path/to/endpoint" --identifier [redacted])
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
// Get URL to call.
url := flag.String("url", "", "URL to call")
identifier := flag.String("identifier", "", "the X-Identifier value")
flag.Parse()
if url == nil || identifier == nil || *url == "" || *identifier == "" {
log.Fatal("'url' and 'identifier' arguments must be provided")
}
// Set up certificates
caPEM, err := ioutil.ReadFile("ca.pem")
if err != nil {
log.Fatalf("unable to read 'ca.pem' in current directory: %v", err)
}
clientPEM, err := ioutil.ReadFile("client.pem")
if err != nil {
log.Fatalf("unable to read 'client.pem' in current directory: %v", err)
}
clientKeyPEM, err := ioutil.ReadFile("client.key.pem")
if err != nil {
log.Fatalf("unable to read 'client.key.pem' in current directory: %v", err)
}
// Make calls.
client, err := configureClient(caPEM, clientPEM, clientKeyPEM)
if err != nil {
log.Fatalf("unable to setup client: %v", err)
}
_, err = performRequest(client, *url, *identifier)
if err != nil {
log.Fatalf("request error: %v", err)
}
log.Printf("request successful")
}
func configureClient(caCertPEM, clientCertPEM, clientKeyPEM []byte) (*http.Client, error) {
// Load the CA certificate.
caCertPool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("configure client: load cert pool: %w", err)
}
// Append root CA cert from parameter
ok := caCertPool.AppendCertsFromPEM(caCertPEM)
if !ok {
return nil, fmt.Errorf("configure client: could not append ca certificate")
}
// Load the client certificate.
clientCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
if err != nil {
return nil, fmt.Errorf("configure client: load client certificate: %w", err)
}
// Setup HTTPS client.
tlsConfig := &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{clientCert},
Renegotiation: tls.RenegotiateOnceAsClient,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
return client, nil
}
func performRequest(client *http.Client, u, identifier string) ([]byte, error) {
if client == nil {
return nil, fmt.Errorf("perform request: nil client")
}
// Prepare request
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, fmt.Errorf("perform request: create GET request: %w", err)
}
// Add same headers as cURL.
req.Header.Add("Accept", "application/json; charset=utf-8")
req.Header.Add("User-Agent", "Apache-HttpClient/4.5.5 (Java/12.0.1)")
req.Header.Add("Accept-Encoding", "gzip, deflate")
req.Header.Add("Connection", "Keep-Alive")
req.Header.Add("X-Identifier", identifier)
// Send request
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("perform request: client do: %w", err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
break
case http.StatusUnauthorized:
return nil, fmt.Errorf("perform request: api response: unauthorized")
case http.StatusBadRequest:
return nil, fmt.Errorf("perform request: api response: bad request")
default:
return nil, fmt.Errorf("perform request: api response: %v", resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("perform request: read response body: %w", err)
}
return data, nil
}
I am trying to read from this google storage bucket:
https://storage.googleapis.com/images.eng.channelmeter.com/avatars/d2d48e49-82be-4cf6-be6e-11ada43c7339
But I am getting this error:
the error is like so:
<Error>
<Code>AccessDenied</Code>
<Message>Access denied.</Message>
<Details>
Anonymous caller does not have storage.objects.get access to images.eng.channelmeter.com/avatars/d2d48e49-82be-4cf6-be6e-11ada43c7339.
</Details>
</Error>
How can I obtain an access token that I can append to url? Something like this:
https://storage.googleapis.com/images.eng.channelmeter.com/avatars/d2d48e49-82be-4cf6-be6e-11ada43c7339?access_token="XXX"
I assume I can make some call using an OAuth library to get a temporary / one-time access token, anyone know how?
Update:
I can get an AccessToken using this technique: https://tanaikech.github.io/2018/12/11/retrieving-access-token-using-service-account-by-googles-oauth2-package-for-golang/
but then when I add the ?access_token=xxx to the URL, I now just get:
<Error>
<Code>AccessDenied</Code>
<Message>Access denied.</Message>
</Error>
damn.
I reproduced your use case. Assuming you have a key.json file of a service account with the wright permissions on the bucket.
To authorize requests from the command line or for testing, you can
use the curl command with the following syntax:
curl -H "Authorization: Bearer ACCESS_TOKEN"
"https://storage.googleapis.com/storage/v1/b/example-bucket/o"
For local testing, you can use the gcloud auth application-default
print-access-token command to generate a token.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
"log"
)
func serviceAccount(credentialFile string) (*oauth2.Token, error) {
b, err := ioutil.ReadFile(credentialFile)
if err != nil {
return nil, err
}
var c = struct {
Email string `json:"client_email"`
PrivateKey string `json:"private_key"`
}{}
json.Unmarshal(b, &c)
config := &jwt.Config{
Email: c.Email,
PrivateKey: []byte(c.PrivateKey),
Scopes: []string{
"https://www.googleapis.com/auth/cloud-platform",
},
TokenURL: google.JWTTokenURL,
}
token, err := config.TokenSource(oauth2.NoContext).Token()
if err != nil {
return nil, err
}
return token, nil
}
func main() {
token, err := serviceAccount("key.json") // Please set here
if err != nil {
fmt.Println(err)
os.Exit(1)
}
url := "https://storage.googleapis.com/storage/v1/b/your-bucket/o/your-file?alt=media"
// Create a Bearer string by appending string access token
var bearer = "Bearer " + token.AccessToken
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
// add authorization header to the req
req.Header.Add("Authorization", bearer)
// Send req using http Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("Error on response.\n[ERRO] -", err)
}
body, _ := ioutil.ReadAll(resp.Body)
log.Println(string([]byte(body)))
}
So this curl request to the paypal payouts API works:
curl --silent -v 'https://api.sandbox.paypal.com/v1/oauth2/token' \
-H "Accept: application/json" \
-H "Accept-Language: en_US" \
-u "${client_id}:${client_secret}" \
-d "grant_type=client_credentials"
one thing I am confused about: the -d option is for data in the body of the HTTP request - does the -d option make it a POST request or is the curl request above a GET request with a body? I would guess that latter but I am unsure given the output of curl --help.
In golang I have:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
const (
PayPalTestClientID = "Aeit5RskDRN8eUUMB0Ud3RjA_z6feWMUHktwlJZMeQMo9A9ulbKK"
PayPalTestSecret = "EAAqyzrOTUWf-OFJCB4BxgXT4xuravL7pnkC8Tn20HYtZExd1mFO"
)
func main() {
//reader := bytes.NewBuffer()
req, err := http.NewRequest("GET", "https://api.sandbox.paypal.com/v1/oauth2/token", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Accept-Language", "en_US")
req.Header.Set("Authorization", fmt.Sprintf("Basic %s:%s", PayPalTestClientID, PayPalTestSecret))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var v interface{}
err = json.Unmarshal(body, &v);
if err != nil {
log.Fatal(err)
}
log.Print(v)
}
the client/secret were obfuscated, so they won't work as written above. But using the real creds I get:
2020/01/31 16:05:07 map[error:invalid_client error_description:Client
Authentication failed]
The real creds do work with the curl command tho.
Note: The credentials provided are valid? Cause i receive a sonorus 401, Authentication Failed.
NOTE: Using the -d in cURL, you are going to send a POST request instead of a GET. Due to this behaviour, your proably want to send a POST request instead of a GET
You can use my little http library: https://github.com/alessiosavi/Requests/
package main
import requests "github.com/alessiosavi/Requests"
func main() {
req, err := requests.InitRequest("https://postman-echo.com/basic-auth", "GET", []byte{}, false, false)
if err != nil {
fmt.Println("ERROR! ", err)
}
req.CreateHeaderList("Accept", "application/json", "Accept-Language", "en_US", "Authorization", "postman:password")
client := &http.Client{}
resp := req.ExecuteRequest(client)
fmt.Println(resp.Dump())
}
You can change the data (URL, post data, headers) with the one that you need for authenticate to the service.
In your case, will be something like this:
package main
import requests "github.com/alessiosavi/Requests"
const (
ID= "Aeit5RskDRN8eUUMB0Ud3RjA_z6feWMUHktwlJZMeQMo9A9ulbKK"
SECRET= "EAAqyzrOTUWf-OFJCB4BxgXT4xuravL7pnkC8Tn20HYtZExd1mFO"
)
func main() {
req, err := requests.InitRequest("https://api.sandbox.paypal.com/v1/oauth2/token", "GET", []byte{"grant_type=client_credentials"}, false, false)
if err != nil {
fmt.Println("ERROR! ", err)
}
req.CreateHeaderList("Accept", "application/json", "Accept-Language", "en_US", "Authorization", ID+":"+SECRET)
client := &http.Client{}
resp := req.ExecuteRequest(client)
fmt.Println(resp.Dump())
}
I am working on a SAAS based product which is built in Angular5 at front end. It uses Go's rest APIs to connect to DB and all back end functionality. I am using JWT token to authenticate the users on this system.
On the back end, I am using Gin framework for API routing and response handling.
The problem I am facing is that when ever I compile latest code on server. The token expires and it asks me to login again in order to generate new token.
The code I am using to generate JWT is given below:
package main
import (
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/contrib/sessions"
)
func CreateToken(user models.User, c *gin.Context) (string, error){
var ip, userAgent string
keyError := config.InitKeys()
if keyError != nil{
return "", keyError
}
if values, _ := c.Request.Header["Ip"]; len(values) > 0 {
ip = values[0]
}
if values, _ := c.Request.Header["User-Agent"]; len(values) > 0{
userAgent = values[0]
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.MapClaims{
"email": user.EmailId,
"exp": time.Now().Add(time.Hour * 8).Unix(),
"role": user.Role,
"name": user.FirstName+" "+user.LastName,
"ip": ip,
"user_agent": userAgent,
"id": user.Id,
})
config.CurrentUserId = user.Id
models.CurrentUser = user
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(config.SignKey))
return tokenString, err
}
And code to compare it with the token that is being sent in the header from front end is:
if values, _ := c.Request.Header["Authorization"]; len(values) > 0 {
bearer := strings.Split(c.Request.Header["Authorization"][0], "Bearer")
bearerToken := strings.TrimSpace(bearer[1])
_, err := merchantDb.GetSession(bson.M{"token": bearerToken})
if err != nil{
errMsg := "Failed: Unauthorized Access."
response := controllers.ResponseController{
config.FailureCode,
config.FailureFlag,
errMsg,
nil,
}
controllers.GetResponse(c, response)
c.Abort()
}else{
var ip, userAgent string
var ipCheck, userAgentCheck bool
if values, _ := c.Request.Header["Ip"]; len(values) > 0 {
ip = values[0]
}
if values, _ := c.Request.Header["User-Agent"]; len(values) > 0{
userAgent = values[0]
}
token, err := jwt.Parse(bearerToken, func(token *jwt.Token) (interface{}, error) {
return config.SignKey, nil
})
if len (token.Claims.(jwt.MapClaims)) > 0{
for key, claim := range token.Claims.(jwt.MapClaims) {
if key == "ip" {
if claim == ip{
ipCheck = true
}
}
if key == "user_agent"{
if claim == userAgent{
userAgentCheck = true
}
}
if key == "role"{
role = claim.(string)
}
if key == "id"{
userId = claim.(float64)
}
if key == "name"{
userName = claim.(string)
}
}
}
merchantDatabase["userid"] = userId
merchantDatabase["role"] = role
merchantDatabase["username"] = userName
c.Keys = merchantDatabase
if err == nil && token.Valid && ipCheck == true && userAgentCheck == true {
c.Next()
} else {
errMsg := "Failed: Invalid Token."
response := controllers.ResponseController{
config.FailureCode,
config.FailureFlag,
errMsg,
nil,
}
controllers.GetResponse(c, response)
c.Abort()
}
}
}else{
errMsg := "Failed: Unauthorized Access."
response := controllers.ResponseController{
config.FailureCode,
config.FailureFlag,
errMsg,
nil,
}
controllers.GetResponse(c, response)
c.Abort()
}
This issue is consuming a lot of time. If issue is known to anyone, Please reply to this post.
Thanks!
I suspect, there is something wrong with your session
_, err := merchantDb.GetSession(bson.M{"token": bearerToken})
or something else, as you didn't share the full code. Perhaps, your SigningKeys are not consistent between the builds.
I wrote a small test for you to prove that jwt tokens don't expire between go builds
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
func CreateToken() (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.MapClaims{
"email": "test#email.com",
"exp": time.Now().Add(time.Hour * 240).Unix(),
//"exp": time.Now().Add(-time.Hour * 8).Unix(),
"role": "test role",
"name": "test name",
"ip": "1.1.1.1",
"user_agent": "test agent",
"id": "123",
})
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte("AllYourBase"))
return tokenString, err
}
func main() {
//tokenString,_ := CreateToken()
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwiZXhwIjoxNTIzNjAwOTM1LCJpZCI6IjEyMyIsImlwIjoiMS4xLjEuMSIsIm5hbWUiOiJ0ZXN0IG5hbWUiLCJyb2xlIjoidGVzdCByb2xlIiwidXNlcl9hZ2VudCI6InRlc3QgYWdlbnQifQ.UCD3P5-ua3qgTvy_-7hmHEVPPZwFCbhmRJNqndBwtes"
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
fmt.Println(token)
fmt.Println(token.Valid)
}
As you can check, the tokenString will be valid for the next 10 days wherever you run this code
I want to implement an Ebay Browse API client credentials grant flow in Go using the oauth2 package. Here is my conf struct:
conf := clientcredentials.Config{
ClientID: "My Client ID",
ClientSecret: "My client Secret",
Scopes: []string{"https://api.ebay.com/oauth/api_scope"},
TokenURL: "https://api.sandbox.ebay.com/identity/v1/oauth2/token",
EndpointParams: url.Values{"redirect_uri": {"the RuName"}},
}
Then I tried to make a request by creating an *http.Request:
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client := conf.Client(ctx)
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
But then I get:
{
"errors" : [ {
"errorId" : 1003,
"domain" : "OAuth",
"category" : "REQUEST",
"message" : "Token type in the Authorization header is invalid:Application",
"longMessage" : "Token type in the Authorization header is invalid:Application"
} ]
}
It turned out, that when making the request to the resource server Go's package sets the Authorization header as:
Authorization: Application Access Token token_string
So, I tried to set the token type to Bearer manually by doing:
tok, err := conf.Token(ctx)
if err != nil {
log.Fatal(err)
}
client := conf.Client(ctx)
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer " tok.AccessToken)
res, err := client.Do(req)
But I continue getting the same error.
So, there is a problem (bug?) with the Ebay's implementation of the OAuth2.0 spec and I had to reimplement the TokenSource of the oauth2/clientcredentials package to set the token type to Bearer not Application Access Token.