Skip to content

Multi-tenancy #152

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

Closed
wants to merge 5 commits into from
Closed

Multi-tenancy #152

wants to merge 5 commits into from

Conversation

moander
Copy link

@moander moander commented Feb 26, 2020

Usage:

var auth = FirebaseAuth.DefaultInstance.TenantManager.AuthForTenant("my-tenant-id");

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

Thank you @moander for taking this up. We should probably have a discussion at #149 about what this public API should look like before getting into code. However, based on what's implemented here, I'd like to call out the following points:

  1. Tenant-scoped custom token creation is not yet supported in Firebase Auth.
  2. The API should look somewhat similar to their Node.js and Java (work in progress) counterparts. That means something like:
var tenantManager = FirebaseAuth.DefaultInstance.TenantManager;
TenanatAwareFirebaseAuth auth = tenantManager.AuthForTenant(tenantId);
await auth.VerifyIdTokenAsync(idToken);

@moander
Copy link
Author

moander commented Feb 27, 2020

@hiranya911 I have added a TenantManager and TenantAwareAuth class.

Tenant-scoped custom token creation is not yet supported in Firebase Auth.

Please elaborate. I used the node sdk as a reference adding tenant_id to the payload:
https://github.com/firebase/firebase-admin-node/blob/2b952d43fd8784151ebdb60b65e88b9fd1888ccf/src/auth/token-generator.ts#L314

@moander moander requested a review from hiranya911 February 27, 2020 09:12
@hiranya911
Copy link
Contributor

Thanks @moander. I see now that the custom token limitation has been since addressed in firebase/firebase-admin-node#708. So your implementation is probably right.

Please use #149 to document the public API resulting from this PR. We need to agree on the names of all public interfaces/methods before we look at code changes. This also has to go through our internal API review process, which I can handle separately.

@moander
Copy link
Author

moander commented Feb 27, 2020

@hiranya911 PR updated with new class names from the Java SDK

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

Thanks @moander. I haven't dug into the tests. But went over the implementation, and pointed out the changes that should be made.

I'd also advise you to only move CreateCustomToken and VerifyIdToken methods to AbstractFirebaseAuth. Otherwise all user management APIs get exposed on TenantAwareFirebaseAuth, which means we need tests for all of that as well.

@@ -152,6 +156,13 @@ internal static FirebaseTokenVerifier CreateIDTokenVerifier(FirebaseApp app)
error = $"{this.shortName} has incorrect audience (aud) claim. Expected {this.ProjectId} "
+ $"but got {payload.Audience}. {projectIdMessage} {verifyTokenMessage}";
}
else if (this.TenantId != payload.Firebase?.TenantId)
{
var expectedTenantId = this.TenantId == null ? "null" : this.TenantId;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var expectedTenantId = this.TenantId == null ? "null" : this.TenantId;
var expectedTenantId = this.TenantId ?? "null";

@@ -42,6 +42,8 @@ internal class FirebaseUserManager : IDisposable

private const string IdTooklitUrl = "https://identitytoolkit.googleapis.com/v1/projects/{0}";

private const string IdTooklitTenantUrl = "https://identitytoolkit.googleapis.com/v1/projects/{0}/tenants/{1}";
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be the v2 endpoint. v1 doesn't support tenant management.

/// <summary>
/// Gets the current instance's <see cref="TenantManager"/>.
/// </summary>
public TenantManager TenantManager => this.IfNotDeleted(() => this.tenantManager.Value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should only be exposed on FirebaseAuth child class.

/// <summary>
/// Gets the tenant ID for this auth instance.
/// </summary>
public string TenantId { get; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Does not belong here.

/// </summary>
public sealed class TenantAwareFirebaseAuth : AbstractFirebaseAuth
{
internal TenantAwareFirebaseAuth(FirebaseAuthArgs args)
Copy link
Contributor

Choose a reason for hiding this comment

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

This constructor should accept a tenant ID as an argument. Tenant ID only makes sense at this level. AbstractFirebaseAuth should be completely unaware of all tenant-scoped operations.


internal string TenantId { get; set; }

internal static FirebaseAuthArgs Create(FirebaseApp app, string tenantId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Logic for creating FirebaseAuthArgs should go into FirebaseAuth and TenantAwareFirebaseAuth classes.

@@ -63,22 +65,23 @@ internal FirebaseUserManager(Args args)
DeserializeExceptionHandler = AuthErrorHandler.Instance,
RetryOptions = args.RetryOptions,
});
this.baseUrl = string.Format(IdTooklitUrl, args.ProjectId);
this.baseUrl = string.Format(args.TenantId != null ? IdTooklitTenantUrl : IdTooklitUrl, args.ProjectId, args.TenantId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Expand this out for readability.

Suggested change
this.baseUrl = string.Format(args.TenantId != null ? IdTooklitTenantUrl : IdTooklitUrl, args.ProjectId, args.TenantId);
if (!string.IsNullOrEmpty(args.TenantId)
{
this.baseUrl = string.Format(IdTooklitTenantUrl, args.ProjectId, args.TenantId);
}
else
{
this.baseUrl = string.Format(IdTooklitUrl, args.ProjectId);
}

/// <param name="tenantId">The tenant ID to manage.</param>
public TenantAwareFirebaseAuth AuthForTenant(string tenantId)
{
if (tenantId != null && !System.Text.RegularExpressions.Regex.IsMatch(tenantId, "^[a-zA-Z0-9-]+$"))
Copy link
Contributor

Choose a reason for hiding this comment

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

Checking string.IsNullOrEmpty() should be sufficient here.

throw new ArgumentException("The tenant ID must be a valid non-empty string.", "tenantId");
}

return this.app.GetOrInit<TenantAwareFirebaseAuth>(nameof(TenantAwareFirebaseAuth) + tenantId, () =>
Copy link
Contributor

Choose a reason for hiding this comment

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

You need a Dictionary<string, TenantAwareFirebaseAuth> attribute in this class. Then you should GetOrInit from that dictionary instead of FirebaseApp.

When the TenantManager instance is disposed, it can go through all values in the dictionary and dispose each of them.

@@ -0,0 +1,60 @@
// Copyright 2018, Google Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

2020

Here and in all other new files.

@hiranya911
Copy link
Contributor

This has been implemented and released in a series of separate PRs.

@hiranya911 hiranya911 closed this Sep 11, 2020
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

Successfully merging this pull request may close these issues.

2 participants