Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve document about authenticating with GitHub Apps #1069

Closed
dorneanu opened this issue Dec 14, 2018 · 9 comments
Closed

Improve document about authenticating with GitHub Apps #1069

dorneanu opened this issue Dec 14, 2018 · 9 comments

Comments

@dorneanu
Copy link

Hi,

I'm currently trying to have a working example how to properly authenticate as a GitHub app in my organization. In the app settings I can find the application ID. Using:

$ curl -i -H "Authorization: Bearer $JWT_TOKEN" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/app/installations

I can also get the installation ID.

Here is my code:

package main

import (
    "context"
    "net/http"
                                                                                                                                                                                                                         "github.com/bradleyfalzon/ghinstallation"
    "github.com/google/go-github/github"
    "github.com/sirupsen/logrus"
)

func main() {
    ctx := context.Background()

    // Set here you application ID and installation id                                                                                                                                                                   
    appId := 111111                                                                                                                                                                                                       
    installationId := 22222       
                                                                                                                                                                                                                                                                                                                                                                                                           
    // Wrap the shared transport for use with defined application and installation IDs                                                                                                                                   
    itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, appId, installationId, "/tmp/key.pem")                                                                                                              
    if err != nil {                                                                                                                                                                                                           
         logrus.Error(err)
    }
    access_token, _ := itr.Token()
    logrus.Infof("Installation access token: %s", access_token)

    // Use installation transport with client
    // NewClient returns a new GitHub API client.
    // If a nil httpClient is provided, http.DefaultClient will be used. To use API methods which require authentication,
    // provide an http.Client that will perform the authentication for you.                                                                                                                                              
    client := github.NewClient(&http.Client{Transport: itr})                                                                                                                                                                                                                                                                                                                                                                                  
 
    // Get org installation                                                                                                                                                                                               
    appService := client.Apps                                                                                                                                                                                            
    appInst, resp, err := appService.FindOrganizationInstallation(ctx, "Scout24")                                                                                                                                        
    if err != nil {                                                                                                                                                                                                          
       logrus.Infof("error: %s", err)
    }

    // installations, resp, err := appService.ListInstallations(ctx, nil)
    logrus.Info(resp)
    logrus.Info(appInst)
}

And the output is:

INFO[0000] Installation access token: v1.4933ed8b240cb8be4xxxxxxxxxxxxxxxxxxx
INFO[0001] error: GET https://api.github.com/orgs/Scout24/installation: 401 A JSON web token could not be decoded []
INFO[0001] &{0xc000130090 0 0 0 0 github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}
INFO[0001] <nil>

Obviously I do get the installation access token however when I try to get the organization installation it failed with:

error: GET https://api.github.com/orgs/Scout24/installation: 401 A JSON web token could not be decoded []

I guess theAuthorization: Bearer $JWT_TOKEN header is not properly set.
Any ideas?

Thanks in advance.

@gmlewis
Copy link
Collaborator

gmlewis commented Dec 15, 2018

Hmmm... I'm not familiar with "github.com/bradleyfalzon/ghinstallation".
Maybe you could ask in that repo to make sure you are getting the token correctly?

Also, the line access_token, _ := itr.Token() raises a big red flag for me... I'm guessing the second field should be err and should not be ignored. Please check to make sure that you are not getting (and ignoring) an error that occurred when you got the access_token.

@pinventado
Copy link

I have the same issue. Was this ever resolved?

@willnorris
Copy link
Collaborator

@gauntface didn't we get this working?

@pinventado
Copy link

If it helps, I debugged the code and found that it was actually able to get an installation token (201 from GitHub). However, the actual response returned by the function is a 401. So I'm not sure what is happening internally.

@JPZ13
Copy link

JPZ13 commented Feb 5, 2020

@dorneanu @willnorris I was running into this issue as well and found that you can only get certain endpoints when you authenticate as an installation, which is what the github.com/bradleyfalzon/ghinstallation package does. For the other Github App endpoints, we need to authenticate with a JWT.

Edit: you can also use the ghinstallation package to authenticate with a JWT using the AppsTransport struct

@dorneanu
Copy link
Author

dorneanu commented Feb 5, 2020

I was able to solve using following code snippets:

...

// RefreshClient will refresh the github app client based on the github installation ID
func (a *GithubAppAuth) RefreshClient(githubInstID int64) {
	itr, err := ghinstallation.New(http.DefaultTransport, a.githubAppID, githubInstID, a.githubAppKey)
	if err != nil {
		log.ErrorWithFields("Couldn't create new transporter", log.Fields{
			"error":        err,
			"githubInstID": githubInstID,
		})
	}

	// Get new token
	client := github.NewClient(&http.Client{Transport: itr})
	token, err := itr.Token(context.Background())
	if err != nil {
		log.ErrorWithFields("Couldn't refresh token", log.Fields{
			"error":        err,
			"githubAppID":  a.githubAppID,
			"githubInstID": githubInstID,
		})
	}
...

Then you can use the token for cloning repositories (I use go-git):

...

// Clone will clone the repository using the repo service
func (i *githubAppInstallation) Clone(org, repoName, dstDir string, depth ...int) (*models.Repo, error) {
	// Authenticating via GitHub App installation token
	// The clone url has this syntax: https://x-access-token:<token>@github.com/<repo name>

	if i.token == "" {
		return nil, errors.New("No auth token was provided")
	}

	// Create URL for clone
	url := fmt.Sprintf("https://x-access-token:%s@github.com/%s", i.token, repoName)

	// If depth is not specified do a shallow clone
	// Otherwise do a full clone
	var gitCloneOpts *git.CloneOptions
	if len(depth) == 0 {
		gitCloneOpts = &git.CloneOptions{
			URL: url,
			// Create a shallow clone and therefore clone the repo
			// with the history truncated only to the last commit
			Depth:             10,
			RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
		}
	} else {
		gitCloneOpts = &git.CloneOptions{
			URL:               url,
			RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
		}
	}

	repo, err := git.PlainClone(dstDir, false, gitCloneOpts)
	if err != nil {
		return nil, err
	}

....

I hope this helps.

@botchniaque
Copy link

I ran into the same problem. The thing is that the client requires different authentication methods for different calls

I solved the problem by creating two separate clients - one for Apps calls, and the other for the rest:

const gheBaseUrl = "https://my-ghe-installation-url/api/v3"

tr := http.DefaultTransport
appId := 99
installationId := 99v

jwtTransport, _ := ghinstallation.NewAppsTransportKeyFromFile(tr, appId, "path/to/key.pem")
installationTokenTransport := ghinstallation.NewFromAppsTransport(jwtTransport, installationId)

installationTokenTransport.BaseURL = gheBaseUrl
jwtTransport.BaseURL = gheBaseUrl

ghClient, _ := github.NewEnterpriseClient(gheBaseUrl, fmt.Sprintf("%s/upload", gheBaseUrl), &http.Client{Transport: installationTokenTransport})
appsGhClient, _ := github.NewEnterpriseClient(gheBaseUrl, fmt.Sprintf("%s/upload", gheBaseUrl),&http.Client{Transport: jwtTransport})

@botchniaque
Copy link

botchniaque commented Jul 9, 2020

related to bradleyfalzon/ghinstallation#39

@gmlewis
Copy link
Collaborator

gmlewis commented Aug 6, 2021

Closing inactive issue for lack of activity/interest. Feel free to reopen.

@gmlewis gmlewis closed this as completed Aug 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants