I am using the excellent goa package to generate my API in Go.
However, I am having an issue with its security middleware and when I submit a bearer token to my controllers, I get "authorization failed" because of "scopes:null". I am using Auth0 for authentication and it is generating the bearer token. The exact error in Postman is:
{"id":"xOUR882s","code":"jwt_security_error","status":401,"detail":"authorization failed: required 'scopes' not present in JWT claim","meta":{"required":["read:meta"],"scopes":null}}
However, my token does include the required scope read:meta. jwt.io gives back the following decoded bearer:
{
"iss": "https://learnlogic.au.auth0.com/",
"sub": "exJMkK7hXX56lrLwoTqna3s0jh7Gq67e#clients",
"aud": "https://api.learn-logic.com",
"exp": 1494855336,
"iat": 1494768936,
"scopes": "read:meta"
}
I am hoping someone can help me workout why, as I don't really understand what is going on in middleware/jwt.go in the goa project which may be found here. My only thought is that something about the Auth0 formatted bearer is not compatible with the parseClaimScopes function in middleware/jwt.go, but I don't know what.
I have the following main.go code:
b, err := ioutil.ReadFile("util/jwt.key")
if err != nil {
return
}
block, _ := pem.Decode([]byte(b))
var cert *x509.Certificate
cert, _ = x509.ParseCertificate(block.Bytes)
rsaPublicKey := cert.PublicKey.(*rsa.PublicKey)
fmt.Println(rsaPublicKey.N)
fmt.Println(rsaPublicKey.E)
fmt.Println(cert)
var keyx = []jwt.Key{rsaPublicKey}
var jwtResolver = jwt.NewSimpleResolver(keyx)
app.UseJWTMiddleware(service, jwt.New(jwtResolver, nil, app.NewJWTSecurity()))
The certificate I am reading in is the same one used in jwt.io to decode the bearer token.
Any help is much appreciated.
According to this ticket https://github.com/goadesign/goa/issues/1228, the problem was due to the fact there was only a support for a claim with a singular name "scope" and not the plural "scopes". This PR https://github.com/goadesign/goa/pull/1399, added the plural version.
Related
This question is not about the difference between Oauth2 and JWT.
I'm building an application using Go and the Gin framework. This application should be able to register users with Facebook SSO. Once a user is registered, I want to make sure that all my endpoints will require an authenticated user. So far I have implemented the Oauth flow, and it works. I'm using the goth/gothic libraries. It looks like this:
router.GET("/auth/:provider", func(c *gin.Context) {
q := c.Request.URL.Query()
q.Add("provider", c.Param("provider"))
c.Request.URL.RawQuery = q.Encode()
// try to get the user without re-authenticating
if gothUser, err := gothic.CompleteUserAuth(c.Writer, c.Request); err == nil {
logger.Debug("Re-using existing credentials", gothUser)
} else {
logger.Debug("Starting auth flow")
gothic.BeginAuthHandler(c.Writer, c.Request)
}
})
router.GET("/callback", func(c *gin.Context) {
user, err := gothic.CompleteUserAuth(c.Writer, c.Request)
l.Debug(user)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
}
})
I'm a bit stuck regarding what to do next. All the articles I could find describe how to get the oauth token, but not how to use it. Should I use the info I get back from facebook (e.g: name, email) to build a JWT token to secure my endpoints? Or is there a way to securely/easily use the oauth token to do that?
I could use a pointer to be on the right track, as you know implementing the auth mechanism is quite important and I couldn't find a guide describing good practices.
I'm trying to use GitHub App and I need to generate a JWT for authenticating (https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-private-key)
I'm trying to do that using Goland.
How can I generate a JWT from PEM private key in Go??
The jwt-go library has all the tools you need, and is fairly well documented. You can find it at https://github.com/golang-jwt/jwt.
Assuming you understand what JWTs are and how they're structured, and that you can get that PEM key as a []byte, the process is roughly:
Add "github.com/golang-jwt/jwt/v4" to your imports.
Create a set of claims, which can include the RegisteredClaims type and any custom claims you may need.
Create the token with jwt.NewWithClaims() - you'll need to provide the appropriate signing method. I've primarily used RS256.
Create the JWT string from the token with token.SignedString().
In practice, it will look something like this:
imports "github.com/golang-jwt/jwt/v4"
type MyCustomClaims struct {
*jwt.RegisteredClaims
FooClaim int
BarClaim string
}
func CreateJWT(pemKey []byte) string {
// expires in 60 minutes
expiration := time.Now().Add(time.Second * 3600)
claims := MyCustomClaims{
RegisteredClaims: &jwt.RegisteredClaims{
Issuer: "Example Code Inc.",
ExpiresAt: jwt.NewNumericDate(expiration),
Subject: "JWT Creation",
},
FooClaim: 123,
BarClaim: "bar",
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
privateKey, _ := jwt.ParseRSAPrivateKeyFromPEM(pemKey)
myJWT, _ := jwt.SignedString(privateKey)
return myJWT
}
I suggest reading code from this repository:
https://github.com/bradleyfalzon/ghinstallation
I don't know why, but the code in the answer from #JesseB above didn't work for me - it always throws: 401 Unauthorized. Although this repository does use golang-jwt package internally
I am trying to get repo list from gitlab using OAuth token.
My code looks something like this ... ("github.com/xanzy/go-gitlab")
repositories := []string{}
client, _ := gitlab.NewClient(gitRepoRequest.Token, gitlab.WithBaseURL("https://gitlab.com/api/v4"))
fmt.Println("client...", client.ContainerRegistry)
projects, _, projectListErr := client.Projects.ListProjects(&gitlab.ListProjectsOptions{})
for _, project := range projects {
fmt.Println("ID===", project.ID)
fmt.Println("NAME===", project.Name)
}
if projectListErr != nil {
// return err
}
I am not able to get the project list.. the "projectListErr" says ...
GET https://gitlab.com/api/v4/projects: 401 {message: 401 Unauthorized}
I am confident about the token value because I am getting list of all branches for a repo using the same token, that code looks like ... ("github.com/go-git/go-git/v5")
rem := git.NewRemote(gitMemory.NewStorage(), &gitConfig.RemoteConfig{
Name: "origin",
URLs: []string{gitBranchesRequest.Repository},
})
refs, listErr := rem.List(&git.ListOptions{
Auth: &gitHttp.BasicAuth{Username: gitUserName, Password: gitBranchesRequest.Token},
})
Does that mean there is an issue with the library that I am using ? github.com/xanzy/go-gitlab
It depends on the type of token you are using.
For instance, a project access token might very well give you access to the list of all branches for a repository (for that project).
But for using the /projects API, 401 means the authentication information is not valid or is missing.
So make sure to use a PAT (Personal Access Token), linked to a user, not a project.
The OP Keval Bhogayata adds in the comments:
I have found the issue.
The library I am using ("xanzy/go-gitlab"), has different client creation functions for different tokens.
I have been using the function that supports personal access token. Instead I was supposed to use "NewOAuthClient" !
// NewOAuthClient returns a new GitLab API client. To use API methods which
// require authentication, provide a valid oauth token.
func NewOAuthClient(token string, options ...ClientOptionFunc) (*Client, error)
I'm trying to implement the EPIC FHIR SMART Backend Services (Backend OAuth 2.0)
on go programming language.
I've created my dev account, uploaded the public key there, and selecting the backend system as the application audience.
I'm pretty sure my jwt token is correct. I've inspected it on jwt.io, the signature is correct. However, I always get this error:
{ "error": "invalid_client", "error_description": null }
I've tried other possible solutions as well such as:
ensuring the expiration date within the jet claim is below 5 minutes
placing the payload in the body with the correct content type, which is application/x-www-form-urlencoded
ensuring to use the sandbox client_id
using the correct jwt sign in method (RS384)
What should I do to resolve this issue?
Btw, I also saw several discussions on the google groups saying that it's worth to wait for one or two days after the dev account is created.
Below is my code. Appreciate the help!
var (
oauth2TokenUrl = "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token"
sandboxClientID = "..."
privateKey = "..."
)
// load private key
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
So(err, ShouldBeNil)
// construct jwt claims
now := time.Now()
claims := jwt.MapClaims{
"iss": sandboxClientID,
"sub": sandboxClientID,
"aud": oauth2TokenUrl,
"jti": uuid.New().String(), // fill with reference id
"exp": now.Add(1 * time.Minute).Unix(), // cannot be more than 5 minutes!
}
log.Info(" => claims:", utility.ToJsonString(claims))
// generate signed token using private key with RS384 algorithm
alg := jwt.SigningMethodRS384
signedToken, err := jwt.NewWithClaims(alg, claims).SignedString(signKey)
So(err, ShouldBeNil)
log.Info(" => signed token", signedToken)
// prepare api call payload
payload := map[string]string{
"grant_type": "client_credentials",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": signedToken,
}
// dispatch the api call
req := resty.New().
R().
EnableTrace().
SetFormData(payload)
res, err := req.Post(oauth2TokenUrl)
So(err, ShouldBeNil)
log.Info(" => response status:", res.StatusCode())
log.Info(" => response header:", res.Header())
log.Info(" => response body:", string(res.Body()))
// parse response
resBody := make(map[string]interface{})
err = json.Unmarshal(res.Body(), &resBody)
So(err, ShouldBeNil)
Fantastic, I got it working now.
The solution is simply waiting! it was confusing because I can't find any explanation about this on the doc, and also the error message is not quite friendly.
in summary, after creating dev app and the public key is uploaded there, we have to wait for a few hours/days, and then the credentials will eventually be usable.
The waiting part is applied to both open epic and app orchard dev accounts.
It seems that Epic has some kind of synchronising mechanism which runs once a day. So waiting after account create is the only solution. Please also note that, in app settings after Endpoint URI change you also have to wait some time.
Error { "error": "invalid_client", "error_description": null } also shows up when redirect_uri param is set to something like localhost:3000.
I encountered this problem too. In my case, I was using "Patients" as the "Application Audience" selected for the Epic SMART on FHIR app. I was able to successfully obtain an authorization code on the test server, but when I attempted to exchange it for an access token I received "invalid_client" error message.
The mistake I made is that the redirect_uri in the HTTP POST must be an absolute URL and must match a redirect URI you have specified for your app. If the redirect URI is invalid, the resulting error message will say "invalid client" (which is misleading).
Here is a sample of the Python code I was using...
data = {
'grant_type': 'authorization_code',
'code': request.GET.get('code'),
'redirect_uri': 'http://127.0.0.1:8000/ehr_connection_complete/', # THIS MUST BE AN ABSOLUTE URL
'client_id': '11111111-2222-3333-4444-555555555555',
}
response = post(url, data)
It felt odd to me that an error with the redirect_uri parameter generates an error message about invalid_client, but it's true with Epic's test FHIR server.
I hope this information helps others.
I'm using this example provided under cloud functions to make a GET request to another GCP API:
import (
"context"
"fmt"
"io"
"google.golang.org/api/idtoken"
)
func makeGetRequest(w io.Writer, targetURL string) error {
ctx := context.Background()
client, err := idtoken.NewClient(ctx, targetURL)
if err != nil {
return fmt.Errorf("idtoken.NewClient: %v", err)
}
resp, err := client.Get(targetURL)
if err != nil {
return fmt.Errorf("client.Get: %v", err)
}
defer resp.Body.Close()
if _, err := io.Copy(w, resp.Body); err != nil {
return fmt.Errorf("io.Copy: %v", err)
}
return nil
}
but when I log the request sent I don't see any authorization header and I get the following error:
"Request
had invalid authentication credentials. Expected OAuth 2 access token, login cookie
or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\"
I have given serviceAccountTokenCreator and the target GCP API admin permissions to the service account that's used to create the cloud function.
Am I misunderstanding what the documentation is saying? It seems like the authorization header should be automatically added.
It might be easier for you to not build the request from scratch and use Client Libraries instead. It provides idiomatic, generated or hand-written code in each language, making the Cloud API simple and intuitive to use. It also handles authentication for you.
From what you're following, the client automatically adds an "Authorization" header so that shouldn't be the problem. You're also trying to follow an example that generates an Identity Token, because calling a Cloud Function endpoint that has authentication requires an Identity token. This is different on your use case, because calling GCP APIs require an OAuth 2 access token. This link explains the difference between the two.
There are ways to generate an access token programmatically such as getting them from the metadata server as I did in my other answer (it's in Python but you can also do it in Golang). However, I suggest learning more on how Client Libraries work and test it for yourself. There are many examples shown on GitHub to get you started.