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.
Related
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 am trying to implement iamcredentials Go API client to generate an Access Token to access some Google APIs via REST API, I am using this code
package main
import (
"context"
"log"
"google.golang.org/api/iamcredentials/v1"
)
func main() {
iamcredentialsService, err := iamcredentials.NewService(context.Background())
if err != nil {
log.Println("error initialize iamcredential Service ", err)
return
}
accessTokenCall := iamcredentialsService.Projects.ServiceAccounts.GenerateAccessToken(
"projects/-/serviceAccounts/some-sa#some-project-id.iam.gserviceaccount.com:generateAccessToken",
&iamcredentials.GenerateAccessTokenRequest{
Scope: []string{
iamcredentials.CloudPlatformScope,
},
},
)
iamResp, err := accessTokenCall.Do()
if err != nil {
log.Println("error generate access token", err)
return
}
log.Println(iamResp)
}
But when I tried to run the above snippet, I got this message
go run main.go
error generate access token googleapi: Error 400: Request contains an invalid argument., badRequest
Is there any way to check which one is causing the above response? I am not sure since there isn't any good example of implementation. Any help would be appreciated, Thanks.
Notes :
I have checked following documentation on this topic https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials and this https://pkg.go.dev/google.golang.org/api/iamcredentials/v1#pkg-overview
I have already setup the Service account using Service Account Token Creator role on IAM and also enabled the IAM API from the console
Also I have added GOOGLE_APPLICATION_CREDENTIALS to the environment variables as suggested
#DanielFarrell is right, you need to remove the :generateAccessToken at the end. Here the documentation in the code. Don't hesitate to explore it, it's open source ;)
// GenerateAccessToken: Generates an OAuth 2.0 access token for a
// service account.
//
// - name: The resource name of the service account for which the
// credentials are requested, in the following format:
// `projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}`. The `-`
// wildcard character is required; replacing it with a project ID is
// invalid.
func (r *ProjectsServiceAccountsService) GenerateAccessToken(name string, generateaccesstokenrequest *GenerateAccessTokenRequest) *ProjectsServiceAccountsGenerateAccessTokenCall {
c := &ProjectsServiceAccountsGenerateAccessTokenCall{s: r.s, urlParams_: make(gensupport.URLParams)}
c.name = name
c.generateaccesstokenrequest = generateaccesstokenrequest
return c
}
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.
I have a GRPC server with APIs that are authorized like:
func (s *MyServer) MyAPI(ctx context.Context, req MyAPIRequest) (MyAPIResponse, error) {
isAuthorized, err = s.IsAuthorized(ctx, req.UserId, Role.User) // other APIs may use a different authorization function than IsAuthorized
if err != nil {
return nil, err
}
if !isAuthorized {
return nil, status.Error(codes.PermissionDenied, "not authorized")
}
// rest of API code
}
I'd like to know how to:
Simplify usage of the authorization logic, like an annotation. I'm more familiar with Java where it would be like #Authorize(ctx = ctx, req = req, role = Role.User) above the func.
Require authorization checks for APIs, so that builds fail if at least one API is missing authorization. I'm using bazel. Note that not all func (s *MyServer) will be APIs.
Here's my idea:
.
Create a YAML file with key value pairs of method name to authorization rule. Example is MyAPI: IsUserIdAuthorizedAsUser which would translate to s.IsAuthorized(ctx, req.UserId, Role.User).
Create an interceptor that looks up authz rule for the request's method name and calls the corresponding authz function.
Have a bazel build rule that parses proto files for rpc, which are all the API method names, and fails if not all of them are in the rule list. I don't know how to do this yet.
Would appreciate any suggestions on my idea or better ways.
I would use an interceptor (grpc.UnaryInterceptor) to handle the authentication / authorization process. It is similar to the classic spring filter (java world).
You can read about interceptors here: https://shijuvar.medium.com/writing-grpc-interceptors-in-go-bf3e7671fe48
You can easily chain multi interceptors or having interceptor per grpc method.
This below is an interceptor that I wrote some months ago (it uses JWT as auth mechanism). You can use it as example:
func (jwt JwtInterceptor) Interceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
md, _ := metadata.FromIncomingContext(ctx)
token := md["jwt"]
if token == nil {
return nil, errors.New("token not present")
}
apiClaims, err := jwt.decoder.Parse(token[0])
if err != nil {
return nil, errors.New("token signature not valid")
}
return handler(context.WithValue(ctx, "jwt", apiClaims), req)
}
I'd like to create Signed URLs to Google Cloud Storage resources from an app deployed using CloudRun.
I set up CloudRun with a custom Service Account with the GCS role following this guide.
My intent was to use V4 Signing to create Signed URLs from CloudRun. There is a guide for this use-case where a file service_account.json is used to generate JWT config. This works for me on localhost when I download the file from google's IAM. I'd like to avoid having this file committed in the repository use the one that I provided in CloudRun UI.
I was hoping that CloudRun injects this service account file to the app container and makes it accessible in GOOGLE_APPLICATION_CREDENTIALS variable but that's not the case.
Do you have a recommendation on how to do this? Thank you.
As you say, Golang Storage Client Libraries require a service account json file to sign urls.
There is currently a feature request open in GitHub for this but you should be able to work this around with this sample that I found here:
import (
"context"
"fmt"
"time"
"cloud.google.com/go/storage"
"cloud.google.com/go/iam/credentials/apiv1"
credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
)
const (
bucketName = "bucket-name"
objectName = "object"
serviceAccount = "[PROJECTNUMBER]-compute#developer.gserviceaccount.com"
)
func main() {
ctx := context.Background()
c, err := credentials.NewIamCredentialsClient(ctx)
if err != nil {
panic(err)
}
opts := &storage.SignedURLOptions{
Method: "GET",
GoogleAccessID: serviceAccount,
SignBytes: func(b []byte) ([]byte, error) {
req := &credentialspb.SignBlobRequest{
Payload: b,
Name: serviceAccount,
}
resp, err := c.SignBlob(ctx, req)
if err != nil {
panic(err)
}
return resp.SignedBlob, err
},
Expires: time.Now().Add(15*time.Minute),
}
u, err := storage.SignedURL(bucketName, objectName, opts)
if err != nil {
panic(err)
}
fmt.Printf("\"%v\"", u)
}
Cloud Run (and other compute platforms) does not inject a service account key file. Instead, they make access_tokens available on the instance metadata service. You can then exchange this access token with a JWT.
However, often times, Google’s client libraries and gcloud works out of the box on GCP’s compute platforms without explicitly needing to authenticate. So if you use the instructions on the page you linked (gcloud or code samples) it should be working out-of-the-box.