Skip to content

Conversation

@hiranya911
Copy link
Contributor

Currently the SDK must be initialized with service account credentials in order to be able to call FirebaseAuth.createCustomToken(). With this PR, the SDK will attempt to sign custom tokens by calling the IAM service in the cloud when the service account credentials are not provided.

// Following will work in GCP managed runtimes where the Metadata service is present FirebaseApp.initializeApp(); String token = FirebaseAuth.getInstance().createCustomToken(uid); 
// Following will work anywhere FirebaseOptions options = new FirebaseOptions.Builder() // need to only specify the service account email here... .setServiceAccount("test-service-account@project-id.iam.gserviceaccount.com") .build(); FirebaseApp.initializeApp(options); String token = FirebaseAuth.getInstance().createCustomToken(uid); 

go/firebase-admin-sign
go/firebase-admin-iam-sign

@hiranya911
Copy link
Contributor Author

Resolves #59

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This basically looks good to me. I left some comment nits.

/**
* Returns the service account email.
*
* @return The service account email set via {@link Builder#setServiceAccount(String)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: s/service account email/client email address of the service account/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/**
* Sets the service account email that should be associated with an app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest to say "client email address of the service account" here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* Sets the service account email that should be associated with an app.
*
* <p>This is used to <a href="https://firebase.google.com/docs/auth/admin/create-custom-tokens">
* create custom auth tokens</a>, when service account credentials are not available. Service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "The email address of a service account can..."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* with that email to sign tokens remotely.
* <li>If the code is deployed in the Google App Engine standard environment, uses the
* <a href="https://cloud.google.com/appengine/docs/standard/java/appidentity/">App Identity
* service</a> to sign tokens.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little long and contains a lot of detail that I would expect to see in the Admin SDK usage docs, but not necessarily in the API documentation. I tried to reduce this down a bit, let me know what you think:

<p>This method generates a token: <li>Using the private key of {@link FirebaseApp}'s service account credentials if provided at initialization. <li>Using the <a href="https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob">IAM service</a> if a service account email was provided via ({@link com.google.firebase.FirebaseOptions.Builder#setServiceAccount(String)}) <li>Using the <a href="https://cloud.google.com/appengine/docs/standard/java/appidentity/">App Identity service</a> if the code is deployed in the Google App Engine standard environment. <li>Using the <a href="https://cloud.google.com/compute/docs/storing-retrieving-metadata"> local Metadata server</a> If the code is deployed in a different GCP-managed environment (e.g. Google Compute Engine). </ol> <p>This method throws an exception when all the above fails. 

I don't feel super strongly about this if you want to keep it detailed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

} catch (IOException e) {
throw new IllegalStateException(
"Failed to initialize FirebaseTokenFactory. Make sure to initialize the SDK "
+ "with a service account credential. Alternatively specify a service account "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/. Alternatively specify/or/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

FirebaseTokenFactory result = this.tokenFactory.get();
if (result == null) {
synchronized (lock) {
result = this.tokenFactory.get();
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Jun 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember my Java 101 class correctly, then you don't need to use an AtomicReference here since you are grabbing a lock already.

http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#synchronization
"Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we'd have to make the variable volatile, to get the double-checked locking semantics to work correctly: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

I don't really have a preference. Drop the AtomicRef and switch to a volatile variable?

byte[] sign(@NonNull byte[] payload) throws IOException;

/**
* Returns the name of the service account used to sign payloads.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the service account email?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Updated comments.

// with the IAM service to sign bytes.
HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(METADATA_SERVICE_URL));
request.getHeaders().set("Metadata-Flavor", "Google");
HttpResponse response = request.execute();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to to call response.disconnect() here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString());

// Should fall back to signing-enabled credential
transport = new MultiRequestMockHttpTransport(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be split into two separate tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@hiranya911
Copy link
Contributor Author

Thanks for the review. Made the suggested changes. Over to @schmidt-sebastian for another look.

* Returns the client email address of the service account.
*
* @return The service account email set via {@link Builder#setServiceAccount(String)}
* @return The client email of service account set via {@link Builder#setServiceAccount(String)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"of THE service account"

* <p>This is used to <a href="https://firebase.google.com/docs/auth/admin/create-custom-tokens">
* create custom auth tokens</a>, when service account credentials are not available. Service
* account email can be found in the {@code client_email} field of a service account JSON.
* create custom auth tokens</a>, when service account credentials are not available. The email
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comma.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@hiranya911
Copy link
Contributor Author

@schmidt-sebastian PTAL at my last commit. This renames serviceAccount to serviceAccountId due to an amendment made to the API proposal.

@hiranya911 hiranya911 merged commit cd213e0 into master Jul 12, 2018
@hiranya911 hiranya911 deleted the hkj-jwt-sign branch July 12, 2018 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants