In order to send Firebase Cloud Messaging with Go, we need to place the access token in the HTTP request header.
On Firebase documentation,
there are examples on how to retrieve the access token using Node.JS, Python and Java:
https://firebase.google.com/docs/cloud-messaging/auth-server
can anyone show the get the access token using Go?
There seems to be many Go packages about Firebase/Google authentication. It's very confusing to understand which ones should be used:
firebase.google.com/go
firebase.google.com/go/auth
github.com/firebase/firebase-admin-go
google.golang.org/api/option
golang.org/x/oauth2
golang.org/x/oauth2/google
github.com/google/google-api-go-client
I'm working on the go firebase SDK to add FCM HTTP v1.
For now it's almost finished, i have to write tests and integration tests, you can check the code here : https://github.com/chemidy/firebase-admin-go/tree/fcm/messaging
I will finish tests and send a PR maybe next week, (it's a little bit tricky to test on ios + android + web)
FCM httpv1 use JSON file for it credentials.
Download it first, then move it to your project.
download JSON credentials in your firebase
second, do :
go get "golang.org/x/oauth2/google"
then use this method to get token
const firebaseScope = "https://www.googleapis.com/auth/firebase.messaging"
type tokenProvider struct {
tokenSource oauth2.TokenSource
}
// newTokenProvider function to get token for fcm-send
func newTokenProvider(credentialsLocation string) (*tokenProvider, error) {
jsonKey, err := ioutil.ReadFile(credentialsLocation)
if err != nil {
return nil, errors.New("fcm: failed to read credentials file at: " + credentialsLocation)
}
cfg, err := google.JWTConfigFromJSON(jsonKey, firebaseScope)
if err != nil {
return nil, errors.New("fcm: failed to get JWT config for the firebase.messaging scope")
}
ts := cfg.TokenSource(context.Background())
return &tokenProvider{
tokenSource: ts,
}, nil
}
func (src *tokenProvider) token() (string, error) {
token, err := src.tokenSource.Token()
if err != nil {
return "", errors.New("fcm: failed to generate Bearer token")
}
return token.AccessToken, nil
}
Then call it in your FCM-Send method :
tp, err := newTokenProvider("yourJSONFileLocation")
if err != nil {
return nil, err
}
token, err := tp.token()
if err != nil {
return nil, err
}
last, add it to header :
r.Header.Add("Authorization", "Bearer "+token)
Done.
i am put header Authorization by used volly
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headerMap = new HashMap<String, String>();
headerMap.put("Content-Type", "application/json");
headerMap.put("Authorization", "Bearer " + key);
Log.v(TAG,"getHeaders "+headerMap);
return headerMap;
}
but send Authorization=Bearer Add..
how change to current
Authorization:Bearer
Related
Users are authenticated in the frontend via
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(googleAuthProvider);
after that I am calling a cloud function with the firebase callable:
const nodeStateCall = functions.httpsCallable('myFunction');
nodeStateCall().then(...);
This workks without any problems and I recieve the jwt token in my go function.
I tried many different variations to authenticate this token. The function which I am using now is idtoken.Validate() from the google.golang.org/api/idtoken pacakge.
My function:
func verifyIdToken(idToken string)(tokenInfo *idtoken.Payload, err error) {
log.Print("running verify Token")
splitToken := strings.Split(idToken, "Bearer ")[1]
log.Print("split Token:"+splitToken)
tokenInfo, err = idtoken.Validate(context.Background(),splitToken,"MYAUDIENCE")
if err != nil {
log.Print("error from tokenInfoCall.Do()")
log.Print(err)
return nil, err
}
log.Print("Finished verify token.")
return tokenInfo, nil
}
but I keep getting "invalid value" when I send the token.
I also tried the oauth2 service:
oauth2Service, err := oauth2.NewService(context.Background())
tokenInfoCall := oauth2Service.Tokeninfo()
tokenInfoCall.IdToken(splitToken)
tokenInfo, err := tokenInfoCall.Do()
which didn't work either.
What do I have to do to validate the firebase jwt token in my go function?
Because you are using firebase service, you need to use this to verify your token:
https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_the_firebase_admin_sdk
Note that you should setup your function in the same project so the library will query correctly.
Is there any way to verify if Firebase Admin SDK Credentials is correct when initializing app with below code?
ctx := context.Background()
opt := option.WithCredentialsFile("path/to/firebase-admin-sdk-cred.json")
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
return nil, err
}
Because I seem to not get any error when I tried to intentionally put the wrong credentials. I check the implementation of the firebase.NewApp() but it seems it only throw error when there is no config. Below is the code of firebase.NewApp()
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
o = append(o, opts...)
if config == nil {
var err error
if config, err = getConfigDefaults(); err != nil {
return nil, err
}
}
pid := getProjectID(ctx, config, o...)
ao := defaultAuthOverrides
if config.AuthOverride != nil {
ao = *config.AuthOverride
}
return &App{
authOverride: ao,
dbURL: config.DatabaseURL,
projectID: pid,
serviceAccountID: config.ServiceAccountID,
storageBucket: config.StorageBucket,
opts: o,
}, nil
}
so Is there any way to check if the credentials is valid during the initialization of Firebase Admin(app) instance because it seems catching error isn't the solution here?
I think the answer is in the documentation:
Some use cases require you to create multiple apps at the same time.
For example, you might want to read data from the Realtime Database of
one Firebase project and mint custom tokens for another project. Or
you might want to authenticate two apps with separate credentials. The
Firebase SDK allows you create multiple apps at the same time, each
with their own configuration information.
Source
I assume the only way to check credentials is invoke an Auth method for example:
client, err := app.Auth(context.Background())
I ended up using google.golang.org/api/transport to force the validation and fail fast
// Check if credential is correct
_, err = transport.Creds(ctx, opt)
if err != nil {
return nil, err
}
The Firebase documentation, Verify ID Tokens, explains how to Verify ID tokens using the Firebase Admin SDK.
But first the Firebase Admin SDK must be setup. After installing the Go Admin SDK, with go get firebase.google.com/go it must be initialized (I assume this code is placed in the HTTP server main function):
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
Question #1: I have no context in my App Engine app because in Migrating from the App Engine Go SDK it states:
Use request.Context() or your preferred context instead of using
appengine.NewContext.
AFACT the request.Context() is available from within an HTTP Handler Function. See the HandleFunc example on golang.org. Using the request context would require initializing the Firebase SDK for every HTTP request!
Question #2: How can I get a reference to my Firebase app from within an HTTP handler?
func main() {
// ------> Initialize the Firebase Go SDK. <------ \\
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
// My usual boilerplate for App Engine.
http.HandleFunc("/api/foo", fooHandler)
http.HandleFunc("/api/bar", barHandler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
There are some very useful looking functions in the firebase/firebase-admin-go repo, but without a context (for the HTTP request?) and no way to share app with my HTTP handlers, I can't work-out how to use them:
// ==================================================================
// https://firebase.google.com/docs/auth/admin/verify-id-tokens
// ==================================================================
func verifyIDToken(ctx context.Context, app *firebase.App, idToken string) *auth.Token {
// [START verify_id_token_golang]
client, err := app.Auth(ctx)
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
log.Fatalf("error verifying ID token: %v\n", err)
}
log.Printf("Verified ID token: %v\n", token)
// [END verify_id_token_golang]
return token
}
As treethought pointed out, context is of little importance for you. You can use context.Background().
The context you are passing to firebase API is not the context you are interested in. That's the context used to goroutines management.
What you need in fact is a context that is provided by the http server you are using. You use that server's context to get the data from the request, parse it, and pass it as idToken.
For example, if you use gin, this is usually achieved by adding middleware that has a contract:
return func(c *gin.Context) {
....
c.Next()
}
Inside it you use your firebase app/client to call verifyIDToken and then decide if you want to chain the request further to your handlers(c.Next()) or abort(c.Abort())
You need to encapsulate the state, in our case firebase client, inside that middleware so you create it as an object/struct with a method that returns a function with the signature specified above.
Here's an example.
I didn't need the flexibility provided by a separate unAuthorized function, and also I store the token inside the cookie, so in my case it became:
package middleware
import (
"bytes"
"context"
"encoding/json"
firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/auth"
"github.com/gin-gonic/gin"
"io/ioutil"
"log"
"net/http"
"strings"
)
type firebaseAuthMiddleware struct {
client *auth.Client
}
func CreateFirebaseMiddleware() *firebaseAuthMiddleware {
app := initFirebaseAppDefault(context.Background())
authClient := createAuthClient(context.Background(), app)
return &firebaseAuthMiddleware{
client: authClient,
}
}
func (fam *firebaseAuthMiddleware) FirebaseAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasSuffix(c.Request.URL.Path, "/query") {
idTokenCookie, err := c.Request.Cookie("FIREBASE_ID_TOKEN")
if idTokenCookie == nil || err != nil {
buildUnauthorizedResponse(c)
return
}
var objmap graphQLMessage
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
}
// Restore the io.ReadCloser to its original state
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
json.Unmarshal(bodyBytes, &objmap)
//stringBody := string(body)
token, err := fam.client.VerifyIDToken(context.Background(), idTokenCookie.Value)
if err != nil {
buildUnauthorizedResponse(c)
return
}
if objmap.Variables["userId"] != token.UID {
buildUnauthorizedResponse(c)
return
}
}
c.Next()
}
}
func buildUnauthorizedResponse(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{
"status": http.StatusUnauthorized,
})
c.Abort()
}
type graphQLMessage struct {
Variables map[string]string
Query string
OperationName string
}
func initFirebaseAppDefault(ctx context.Context) *firebase.App {
app, err := firebase.NewApp(ctx, nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
return app
}
func createAuthClient(ctx context.Context, app *firebase.App) *auth.Client {
client, err := app.Auth(ctx)
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
return client
}
And then register the middleware in main:
package main
import (
"context"
"errors"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
mw "github.com/wnd-engineering/storeback/middleware"
"log"
"runtime/debug"
)
func main() {
r := gin.Default()
r.Use(mw.CreateFirebaseMiddleware().FirebaseAuthMiddleware())
r.POST("/query", graphqlHandler())
r.GET("/", playgroundHandler())
....
}
I think you should be able to just create a new context context.Background() for example.
I'm making an app that needs to send printing jobs through Google Cloud Printing to two printers owned by me (i.e., the printers are always the same one and does not belong to the user). I've set up the printers with Google Cloud Printing and it is now accessible from my Google Account.
Now, how can I access this account's printers through the API? I have found some documentation here that says that I need to authenticate myself when making requests. It seems to me that the authentication should be done with OAuth2. But the instructions on how to do that are lacking for a beginner. I've gotten so far as to getting my OAuth client ID and secret (step 1 in the OAuth link). But for step 2, I have no idea what to do.
It says:
Before your application can access private data using a Google
API, it must obtain an access token that grants access to that API. A
single access token can grant varying degrees of access to multiple
APIs.
But doesn't explain how to obtain this access token. I looked at this SO question where OP seems to have been able to get this access token, but I can't understand how he did it.
Could someone please explain how to get an access token to use with Google Cloud Printing? Or a good resource which explains how?
PS. The printing functionality is triggered by a firebase function. Would this help us get the access token, considering firebase is also made by Google?
I ran into the same issue and came up with this two-step solution:
Create an OAuth2 client in your Google Cloud Console as described here
and download its client credentials from the console and copy & past its json content to credJSON in the code snippet below.
Run the code below.
Follow the auth link and authorize your OAuth2 client to access Googel Cloud Printers with your Google account.
Copy & paste the auth code to the script
Once you obtained a refresh token make sure to store it in the variable refreshToken
Don't forget to update the proxy name.
package main
import (
"context"
"fmt"
"log"
"github.com/google/cloud-print-connector/gcp"
"github.com/google/cloud-print-connector/lib"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
credJSON = ``
refreshToken = ""
// Find the proxy in the Advanced Details of your printer at https://www.google.com/cloudprint#printers
proxy = "HP"
)
func main() {
// Obtain the OAuth config
config, err := google.ConfigFromJSON([]byte(credJSON), gcp.ScopeCloudPrint)
if err != nil {
log.Fatalf("Failed to obtain OAuth config: %v", err)
}
// If no request token is present, obtain a new one
if refreshToken == "" {
// Get the auth link
authLink := config.AuthCodeURL(uuid.New().String(), oauth2.AccessTypeOffline)
log.Printf("Follow the link to obtain an auth code: %s", authLink)
fmt.Printf("Paste your auth code here: ")
var code string
fmt.Scanln(&code)
// Get a token form the auth code
token, err := config.Exchange(context.Background(), code, oauth2.AccessTypeOffline)
if err != nil {
log.Fatalf("Failed to obtain OAuth token: %v", err)
}
if token.RefreshToken != "" {
refreshToken = token.RefreshToken
} else {
refreshToken = token.AccessToken
}
log.Printf("Refresh token: %s", refreshToken)
}
// Connect to Google Cloud Print
jobCh := make(chan *lib.Job)
client, err := gcp.NewGoogleCloudPrint(lib.DefaultConfig.GCPBaseURL, refreshToken, refreshToken, proxy, config.ClientID, config.ClientSecret, config.Endpoint.AuthURL, config.Endpoint.TokenURL, lib.DefaultConfig.NativeJobQueueSize, jobCh, true)
if err != nil {
log.Fatalf("Failed to connect to GCP: %v", err)
}
// List all printers
printers, _, err := client.ListPrinters()
if err != nil {
log.Fatalf("Failed to list printers: %v", err)
}
for _, p := range printers {
log.Printf("Name: %s UUID: %s", p.Name, p.UUID)
}
}
Please refer to following documentation:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount?authuser=1
I followed the same steps specified in the doc and was able to obtain the access token. First make Google Service Account, select furnish new private key. You ll have service account email addresss and private key. Using these credentials, you can obtain your access token. Below is the source code in Golang , this ll surely help you.
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"net/http"
"encoding/json"
"bytes"
)
type MyCustomClaims struct {
Scope string `json:"scope,omitempty"`
jwt.StandardClaims
}
type Toke struct {
Access string `json:"access_token,omitempty"`
Type string `json:"token_type,omitempty"`
Expire string `json:"expires_in,omitempty"`
}
func main() {
key := []byte("<your private key>")
key1, _ := jwt.ParseRSAPrivateKeyFromPEM(key)
claims := MyCustomClaims{
"https://www.googleapis.com/auth/cloudprint",
jwt.StandardClaims{
IssuedAt: <currrent-epoch-time>, // eg 1234566000
ExpiresAt: <currrent-epoch-time + 3600>, // 3600 secs = 1hour, so expires in 1 hour, eg 1234569600
Issuer: "<your service account email>",
Audience: "https://www.googleapis.com/oauth2/v4/token",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
ss, err := token.SignedString(key1)
if err != nil {
fmt.Println(err)
}
fmt.Println(ss)
url := "https://www.googleapis.com/oauth2/v4/token"
any := "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + ss
a := []byte(any)
b := bytes.NewBuffer(a)
var tok Toke
req, err := http.NewRequest("POST", url, b)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
} else {
json.NewDecoder(resp.Body).Decode(&tok)
}
fmt.Println("----------- Access Token -----------------")
fmt.Println("Access: ", tok.Access)
}
I am using Jon Calhoun's Go MVC framework from github.
The framework uses julienschmidt/httprouter as its only dependency.
I have a similar main method as found in the example:
func main() {
//register routes
router := httprouter.New()
//default
router.GET("/", controllers.Login.Perform(controllers.Login.Index))
//login
router.GET("/login", controllers.Login.Perform(controllers.Login.Login))
router.POST("/login", controllers.Login.Perform(controllers.Login.PostLogin))
//dashboard
router.GET("/dashboard", controllers.Dashboard.Perform(controllers.Dashboard.Index))
//listen and handle requests
log.Fatal(http.ListenAndServe(":"+helpers.ReadConfig("port_http"), router))
}
I make a post to the login url, and it calls the following method:
func (self LoginController) PostLogin(w http.ResponseWriter, r *http.Request, ps httprouter.Params) error {
//create our api url
var url = helpers.ReadConfig("api") + "login"
//fill model to post
login := models.LoginModel{
Password: r.FormValue("password"),
Email: r.FormValue("username"),
}
//render json from model
bytes, err := json.Marshal(login)
if err != nil {
panic(err)
}
//post to the API helpers
var resp = helpers.ApiPost(url, r, string(bytes))
//check response if successful
if resp.Code != constants.ApiResp_Success {
//TODO: Handle API Errors
login.Password = ""
errors := make(map[int]string)
errors[1] = "Please provide valid credntials."
login.Common = models.CommonModel{
ErrorList: errors,
}
return views.Login.Index.Render(w, login, helpers.AcceptsGzip(r))
}
log.Println("---Redirect--")
http.Redirect(w, r, "/dashboard", 307)
log.Println("-----")
return views.Dashboard.Index.Render(w, login, helpers.AcceptsGzip(r))
}
Basically, if the login was not correct I return the same view. If the login is correct I want to redirect to another method in a different controller.
However when I call http.Redirect(w, r, "/dashboard", 307), it returns the following error:
http: multiple response.WriteHeader calls
I'm not sure exactly why this is happening, but I suspect that it has something to do with my listener calling the Perform function, which creates a http.handler, as shown below.
func (c *Controller) Perform(a Action) httprouter.Handle {
return httprouter.Handle(
func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
//set response headers
//TODO: set appropriate responce headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Cache-Control", "public, max-age=0")
w.Header().Set("Token", "NOT-A-VALID-TOKEN")
w.WriteHeader(200)
if err := a(w, r, ps); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}
Does anyone have any idea how to redirect using this MVC framework? Or have a one off solution?
http.ResponseWriter's WriteHeader method can only be called once per HTTP response, for obvious reasons: You can only have a single response code, and you can only send the headers once.
The error you see means that it is called a second time on the same response.
Your middleware calls:
w.WriteHeader(200)
Then your handler also calls:
http.Redirect(w, r, "/dashboard", 307)
log.Println("-----")
return views.Dashboard.Index.Render(w, login, helpers.AcceptsGzip(r))
Your middleware should never call WriteHeader, until after the fate of the response is known.
Further, without knowing about your particular MVC framework, it seems possible that after you send the 307 status, then you also tell the MVC framework to render a response, which may also call WriteHeader again.