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

[Feature Request] Support for ServiceAccountID in Custom Token Creation #971

Open
kmurata08 opened this issue Dec 25, 2024 · 12 comments
Open
Assignees

Comments

@kmurata08
Copy link

kmurata08 commented Dec 25, 2024

Describe the feature you would like to see

Thank you for your continuous development and maintenance of the Firebase PHP Admin SDK.

Feature Request Description

I would like to request support for specifying a service account ID when creating custom tokens, similar to how it's implemented in other Firebase Admin SDKs.

Current Situation

Currently, the Firebase PHP Admin SDK only supports creating custom tokens using the service account credentials loaded during initialization. There's no way to specify a different service account ID for token signing.

Proposed Feature

Add support for specifying a service account ID when creating custom tokens. This would allow using a different service account for token signing than the one used to initialize the SDK.

The feature would be particularly useful when running in Google-managed environments where you want to maintain consistency across different parts of your application by using a specific service account for token signing, without needing to include service account JSON files in your code.

Example Implementation (in Go)

Here's how it works in the Go Admin SDK:

conf := &firebase.Config{
    ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)

Related Documentation

Using service account ID

Would love to hear your thoughts on this feature request. Thank you for considering it!

@jeromegamez
Copy link
Member

The factory has a withProjectId() method, would that help? This way you could use the same service account with different projects, or two different service accounts for the same project, by instantiating two factories.

I hope I got this correctly - I'm currently on the road and can't look into it in detail, but if this isn't it, I will have a closer look once I can get back to my desk 🤞🏻

@kmurata08
Copy link
Author

Thank you for your response. I understand you're busy, and there's no rush - please feel free to look into this whenever you have time.
I apologize if my understanding of withProjectId() is insufficient. Let me explain my use case in more detail:
What I'm trying to achieve is to issue an ID token signed by Project A while using Project B's service account credentials. Here's the issue I'm encountering with the current implementation:

  1. Initialize Firebase Admin SDK with Project B's service account credentials
  2. Set Project A using withProjectId()
  3. Create custom token and exchange it for an ID token
  4. The resulting ID token has Project B as its aud claim because it was signed using Project B's service account

When trying to verify this ID token in Project A, the verification fails because the token's aud claim is Project B, not Project A.
I believe that if we could specify a service account ID during SDK initialization (similar to other Firebase Admin SDKs), we could control which service account is used for signing custom tokens independently of the credentials used for SDK authentication, which would resolve this issue.
Does this help clarify my use case? Please let me know if you need any additional information.

@jeromegamez
Copy link
Member

Thank you for the detailed explanation, I really appreciate it! I will look into it! Please keep in mind that I have to implement it in a way that doesn't break backwards compatibility - it might be no problem, but I'll have to check, so, thank you for your patience! 🙏🏻

@kmurata08
Copy link
Author

Thank you for your quick response! I completely understand about the backwards compatibility concerns. Please take your time to review and implement it properly. I really appreciate you looking into this request! 🙏

@jeromegamez
Copy link
Member

jeromegamez commented Jan 3, 2025

I've looked into it, and I'm not sure this is actually possible, in PHP at least (I looked into the official Admin SDKs but couldn't find something that helped me).

The Custom Token Generator uses the credentials created with the help of the google/auth library. If a service account JSON string or file path is given, the Service Account credentials are created with them, otherwise, the library determines the credentials from the environment. Those credentials are then used to sign the payload of a generated custom token.

Are you sure just overriding the iss and sub claims of the custom token to the other service account ID is enough? If the SDK was initialized with Service Account A, tokens for Service Account B would still be signed with the A credentials.

You mentioned how it's done in the Go Admin SDK, but this looks like the same initialization as in the PHP SDK - you give it a Service Account ID, and then the whole app uses this service account ID.

There are the GCECredentials provided by the google/auth library, which accepts a $serviceAccountIdentity argument, but this one is never set by default, and also, the PHP SDK doesn't explicitly checks the environment in which it is running by its own and lets google/auth handle it.

I probably could make something work like

$factory = new Factor();
$authForCurrentProject = $factory->createAuth();
$authForOtherProject = $factory->forServiceAccount($serviceAccountId)->createAuth();

but this would only work on GCE, and I don't know if it's even possible, because I would have to determine the GCE credentials myself instead of relying on google/auth (which doesn't even support using the other account ID).

And that's not even taking into account that I'm not sure I could implement this without breaking backward compatibility.

I hope my ramblings somehow make sense, I'm writing this down as I'm looking into the Firebase Docs and the source code of the other SDKs. Perhaps this is something that could be made easier by introducing a code change in the PHP google/auth library, but this would take time - if they even would approve it.

Perhaps I can figure out a solution when I look at it with a fresh mind (it's currently 2am where I am, and I spent a good 2h on this), but, at the moment, I'm not confident this can be done with the current setup.

@jeromegamez
Copy link
Member

What I found were inpersonated and external service account credentials

But they seem to require service account JSONs with different type fields, so not just something that can be fetched from the environment.

@jeromegamez
Copy link
Member

Again, I could add a method like Factory::withServiceAccountID($id) that would only work on GCE, but I would still not be able to rely on the google/auth library to create the credentials.

At least that's what I'm seeing right now - please let me know if I got something wrong.

@kmurata08
Copy link
Author

Thank you so much for spending a considerable amount of time investigating this, especially late at night! I really appreciate your thorough technical explanation and research.

I was under the impression from the documentation that other SDKs allow separation between the service account ID used for token signing and the one used for Admin SDK operations. However, I'm now less confident about this understanding, especially considering the complexity around signature verification across different projects.

I will look into alternative approaches on my end, so please don't spend any more time on this implementation. Thank you again for your detailed investigation and clear communication about the technical constraints!

@jeromegamez
Copy link
Member

Thank you for providing more details and the link to the docs, this helped me figure things out a little better! 🙏🏻

Do I understand you correctly that you can create a custom token for project B that is signed by service account A? If that's so, it might be possible to adapt the PHP SDK to enable this. I can't update the Auth class itself because it implements an interface - and if I would change the interface, this would be a breaking change. This means, I can't change the method arguments from

Auth::createCustomToken(Stringable|string $uid, array $claims = [], $ttl = 3600): UnencryptedToken {}

to something like

Auth::createCustomToken(Stringable|string $uid, array $claims = [], $ttl = 3600, string $serviceAccountID): UnencryptedToken {}

From the top of my head, I could imagine it working like this:

$factory = new Factory();
$defaultAuth = $factory->createAuth();

$authForCustomToken = $factory
    ->withClientEmailForCustomTokenGeneration('...')
    ->createAuth();

$dafaultCustomToken = $defaultAuth->createCustomToken('uid') 
$customTokenForOtherServiceAccount = $authForCustomToken->createCustomToken('uid') 

Both instances of Auth would behave exactly the same, except for the custom token generation. This is an ugly way of doing it, but unfortunately the only way I can currently think of.

When using the Laravel Package or the Symfony bundle, it would be quite complicated to implement this, though :/

@jeromegamez
Copy link
Member

If you want, I can create a PR with the change proposed above so that you could test if that works - once we confirm that it does, I can make it as "pretty" as possible and figure out Laravel/Symfony later.

@jeromegamez
Copy link
Member

I went ahead and created a PR, let's continue to iterate in the PR discussion 🙏🏻

@kmurata08
Copy link
Author

Thank you so much for not only finding a potential solution but also creating a PR! I truly appreciate your dedication to making this work.

Yes, your understanding is correct - we want to be able to sign custom tokens for project B using service account A. This is exactly what we're looking to achieve.

I'll need some time to test the implementation. I'll provide feedback in the PR discussion once I've had a chance to try it out.

Thank you again for all your help! 🙏

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

2 participants