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

Enable multitenancy in Firebase.Auth project #218

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Auth/AuthCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/// </summary>
public abstract class AuthCredential
{
public string TenantId { get; set; }
public FirebaseProviderType ProviderType { get; set; }
}
}
29 changes: 15 additions & 14 deletions src/Auth/FirebaseAuthClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public event EventHandler<UserEventArgs> AuthStateChanged
}
}

public async Task<UserCredential> SignInWithRedirectAsync(FirebaseProviderType authType, SignInRedirectDelegate redirectDelegate)
public async Task<UserCredential> SignInWithRedirectAsync(FirebaseProviderType authType, SignInRedirectDelegate redirectDelegate, string tenantId = null)
{
var provider = this.config.GetAuthProvider(authType);

Expand All @@ -92,15 +92,15 @@ public async Task<UserCredential> SignInWithRedirectAsync(FirebaseProviderType a

await this.CheckAuthDomain();

var continuation = await oauthProvider.SignInAsync();
var redirectUri = await redirectDelegate(continuation.Uri).ConfigureAwait(false);
var continuation = await oauthProvider.SignInAsync(tenantId);
var redirectUri = await redirectDelegate(continuation.Uri).ConfigureAwait(false);

if (string.IsNullOrEmpty(redirectUri))
{
return null;
}

var userCredential = await continuation.ContinueSignInAsync(redirectUri).ConfigureAwait(false);
var userCredential = await continuation.ContinueSignInAsync(redirectUri, tenantId).ConfigureAwait(false);

this.SaveToken(userCredential.User);

Expand All @@ -120,9 +120,9 @@ public async Task<UserCredential> SignInWithCredentialAsync(AuthCredential crede
return userCredential;
}

public async Task<UserCredential> SignInAnonymouslyAsync()
public async Task<UserCredential> SignInAnonymouslyAsync(string tenantId = null)
{
var response = await this.signupNewUser.ExecuteAsync(new SignupNewUserRequest { ReturnSecureToken = true }).ConfigureAwait(false);
var response = await this.signupNewUser.ExecuteAsync(new SignupNewUserRequest { ReturnSecureToken = true, TenantId = tenantId }).ConfigureAwait(false);
var credential = new FirebaseCredential
{
ExpiresIn = response.ExpiresIn,
Expand All @@ -144,51 +144,52 @@ public async Task<UserCredential> SignInAnonymouslyAsync()
return new UserCredential(user, null, OperationType.SignIn);
}

public async Task<FetchUserProvidersResult> FetchSignInMethodsForEmailAsync(string email)
public async Task<FetchUserProvidersResult> FetchSignInMethodsForEmailAsync(string email, string tenantId = null)
{
await this.CheckAuthDomain().ConfigureAwait(false);

var request = new CreateAuthUriRequest
{
ContinueUri = this.config.RedirectUri,
Identifier = email
Identifier = email,
TenantId = tenantId
};

var response = await this.createAuthUri.ExecuteAsync(request).ConfigureAwait(false);

return new FetchUserProvidersResult(email, response.Registered, response.SigninMethods, response.AllProviders);
}

public async Task<UserCredential> SignInWithEmailAndPasswordAsync(string email, string password)
public async Task<UserCredential> SignInWithEmailAndPasswordAsync(string email, string password, string tenantId = null)
{
await this.CheckAuthDomain().ConfigureAwait(false);

var provider = (EmailProvider)this.config.GetAuthProvider(FirebaseProviderType.EmailAndPassword);
var result = await provider.SignInUserAsync(email, password).ConfigureAwait(false);
var result = await provider.SignInUserAsync(email, password, tenantId).ConfigureAwait(false);

this.SaveToken(result.User);

return result;
}

public async Task<UserCredential> CreateUserWithEmailAndPasswordAsync(string email, string password, string displayName = null)
public async Task<UserCredential> CreateUserWithEmailAndPasswordAsync(string email, string password, string displayName = null, string tenantId = null)
{
await this.CheckAuthDomain().ConfigureAwait(false);

var provider = (EmailProvider)this.config.GetAuthProvider(FirebaseProviderType.EmailAndPassword);
var result = await provider.SignUpUserAsync(email, password, displayName).ConfigureAwait(false);
var result = await provider.SignUpUserAsync(email, password, displayName, tenantId).ConfigureAwait(false);

this.SaveToken(result.User);

return result;
}

public async Task ResetEmailPasswordAsync(string email)
public async Task ResetEmailPasswordAsync(string email, string tenantId = null)
{
await this.CheckAuthDomain().ConfigureAwait(false);

var provider = (EmailProvider)this.config.GetAuthProvider(FirebaseProviderType.EmailAndPassword);
await provider.ResetEmailPasswordAsync(email).ConfigureAwait(false);
await provider.ResetEmailPasswordAsync(email, tenantId).ConfigureAwait(false);
}

public void SignOut()
Expand Down
9 changes: 6 additions & 3 deletions src/Auth/FirebaseAuthException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ public AuthErrorReason Reason
/// </summary>
public class FirebaseAuthLinkConflictException : FirebaseAuthException
{
public FirebaseAuthLinkConflictException(string email, IEnumerable<FirebaseProviderType> providers)
: base($"An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address: {email}", AuthErrorReason.AccountExistsWithDifferentCredential)
public FirebaseAuthLinkConflictException(string email, IEnumerable<FirebaseProviderType> providers, string tenantId = null)
: base($"An account already exists{(string.IsNullOrEmpty(tenantId) ? "" : $" in tenant {tenantId}")} with the same email address but different sign-in credentials. Sign in using a provider associated with this email address: {email}", AuthErrorReason.AccountExistsWithDifferentCredential)
{
this.Email = email;
this.Email = email;
this.Providers = providers;
this.TenantId = tenantId;
}

public string Email { get; }

public string TenantId { get; set; }

public IEnumerable<FirebaseProviderType> Providers { get; }
}

Expand Down
15 changes: 8 additions & 7 deletions src/Auth/IFirebaseAuthClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,30 @@ public interface IFirebaseAuthClient
/// <summary>
/// Gets a list of sign-in methods for given email. If there are no methods, it means the user with given email doesn't exist.
/// </summary>
Task<FetchUserProvidersResult> FetchSignInMethodsForEmailAsync(string email);
Task<FetchUserProvidersResult> FetchSignInMethodsForEmailAsync(string email, string tenantId = null);

/// <summary>
/// Creates a new user with given email, password and display name (optional) and signs this user in.
/// </summary>
Task<UserCredential> CreateUserWithEmailAndPasswordAsync(string email, string password, string displayName = null);
Task<UserCredential> CreateUserWithEmailAndPasswordAsync(string email, string password, string displayName = null, string tenantId = null);

/// <summary>
/// Signs in as an anonymous user.
/// </summary>
Task<UserCredential> SignInAnonymouslyAsync();
Task<UserCredential> SignInAnonymouslyAsync(string tenantId = null);

/// <summary>
/// Signs in via third party OAuth providers - e.g. Google, Facebook etc.
/// </summary>
/// <param name="authType"> Type of the provider, must be an oauth one. </param>
/// <param name="redirectDelegate"> Delegate which should invoke the passed uri for oauth authentication and return the final redirect uri. </param>
Task<UserCredential> SignInWithRedirectAsync(FirebaseProviderType authType, SignInRedirectDelegate redirectDelegate);
/// <param name="tenantId"> Optional tenant id..</param>
Task<UserCredential> SignInWithRedirectAsync(FirebaseProviderType authType, SignInRedirectDelegate redirectDelegate, string tenantId = null);

/// <summary>
/// Signs in with email and password. If the email &amp; password combination is incorrect, <see cref="FirebaseAuthException"/> is thrown.
/// </summary>
Task<UserCredential> SignInWithEmailAndPasswordAsync(string email, string password);
Task<UserCredential> SignInWithEmailAndPasswordAsync(string email, string password, string tenantId = null);

/// <summary>
/// Sign in with platform specific credential. For example:
Expand All @@ -57,11 +58,11 @@ public interface IFirebaseAuthClient
/// <summary>
/// Sends a password reset email to given address.
/// </summary>
Task ResetEmailPasswordAsync(string email);
Task ResetEmailPasswordAsync(string email, string tenantId = null);

/// <summary>
/// Signs current user out.
/// </summary>
void SignOut();
}
}
}
2 changes: 1 addition & 1 deletion src/Auth/Providers/AppleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public AppleProvider()
this.AddScopes(DefaultEmailScope);
}

public static AuthCredential GetCredential(string accessToken) => GetCredential(FirebaseProviderType.Apple, accessToken, OAuthCredentialTokenType.AccessToken);
public static AuthCredential GetCredential(string accessToken, string tenantId = null) => GetCredential(FirebaseProviderType.Apple, accessToken, tenantId, OAuthCredentialTokenType.AccessToken);

public override FirebaseProviderType ProviderType => FirebaseProviderType.Apple;

Expand Down
24 changes: 14 additions & 10 deletions src/Auth/Providers/EmailProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,42 @@ internal override void Initialize(FirebaseAuthConfig config)
this.linkAccount = new SetAccountLink(config);
}

public static AuthCredential GetCredential(string email, string password)
public static AuthCredential GetCredential(string email, string password, string tenantId = null)
{
return new EmailCredential
{
ProviderType = FirebaseProviderType.EmailAndPassword,
Email = email,
Password = password
Password = password,
TenantId = tenantId
};
}

public Task ResetEmailPasswordAsync(string email)
public Task ResetEmailPasswordAsync(string email, string tenantId = null)
{
var request = new ResetPasswordRequest
{
Email = email
Email = email,
TenantId = tenantId
};

return this.resetPassword.ExecuteAsync(request);
}

public Task<UserCredential> SignInUserAsync(string email, string password)
public Task<UserCredential> SignInUserAsync(string email, string password, string tenantId = null)
{
return this.SignInWithCredentialAsync(GetCredential(email, password));
return this.SignInWithCredentialAsync(GetCredential(email, password, tenantId));
}

public async Task<UserCredential> SignUpUserAsync(string email, string password, string displayName)
public async Task<UserCredential> SignUpUserAsync(string email, string password, string displayName, string tenantId = null)
{
var authCredential = GetCredential(email, password);
var authCredential = GetCredential(email, password, tenantId);
var signupResponse = await this.signupNewUser.ExecuteAsync(new SignupNewUserRequest
{
Email = email,
Password = password,
ReturnSecureToken = true
ReturnSecureToken = true,
TenantId = tenantId
}).ConfigureAwait(false);

var credential = new FirebaseCredential
Expand Down Expand Up @@ -105,7 +108,8 @@ protected internal override async Task<UserCredential> SignInWithCredentialAsync
{
Email = ec.Email,
Password = ec.Password,
ReturnSecureToken = true
ReturnSecureToken = true,
TenantId = ec.TenantId
}).ConfigureAwait(false);

var user = await this.GetUserInfoAsync(response.IdToken).ConfigureAwait(false);
Expand Down
2 changes: 1 addition & 1 deletion src/Auth/Providers/FacebookProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public FacebookProvider()
this.AddScopes(DefaultEmailScope);
}

public static AuthCredential GetCredential(string accessToken) => GetCredential(FirebaseProviderType.Facebook, accessToken, OAuthCredentialTokenType.AccessToken);
public static AuthCredential GetCredential(string accessToken, string tenantId = null) => GetCredential(FirebaseProviderType.Facebook, accessToken, tenantId, OAuthCredentialTokenType.AccessToken);

public override FirebaseProviderType ProviderType => FirebaseProviderType.Facebook;

Expand Down
2 changes: 1 addition & 1 deletion src/Auth/Providers/GithubProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public class GithubProvider : OAuthProvider
{
public static AuthCredential GetCredential(string accessToken) => GetCredential(FirebaseProviderType.Github, accessToken, OAuthCredentialTokenType.AccessToken);
public static AuthCredential GetCredential(string accessToken, string tenantId = null) => GetCredential(FirebaseProviderType.Github, accessToken, tenantId, OAuthCredentialTokenType.AccessToken);

public override FirebaseProviderType ProviderType => FirebaseProviderType.Github;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Auth/Providers/GoogleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public GoogleProvider()
this.AddScopes(DefaultProfileScope, DefaultEmailScope);
}

public static AuthCredential GetCredential(string token, OAuthCredentialTokenType tokenType = OAuthCredentialTokenType.AccessToken) => GetCredential(FirebaseProviderType.Google, token, tokenType);
public static AuthCredential GetCredential(string token, OAuthCredentialTokenType tokenType = OAuthCredentialTokenType.AccessToken, string tenantId = null) => GetCredential(FirebaseProviderType.Google, token, tenantId, tokenType);

public override FirebaseProviderType ProviderType => FirebaseProviderType.Google;

Expand Down
2 changes: 1 addition & 1 deletion src/Auth/Providers/MicrosoftProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public MicrosoftProvider()
this.AddScopes(DefaultScopes);
}

public static AuthCredential GetCredential(string accessToken) => GetCredential(FirebaseProviderType.Microsoft, accessToken, OAuthCredentialTokenType.AccessToken);
public static AuthCredential GetCredential(string accessToken, string tenantId = null) => GetCredential(FirebaseProviderType.Microsoft, accessToken, tenantId, OAuthCredentialTokenType.AccessToken);

public override FirebaseProviderType ProviderType => FirebaseProviderType.Microsoft;
}
Expand Down
8 changes: 5 additions & 3 deletions src/Auth/Providers/OAuthContinuation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ internal OAuthContinuation(FirebaseAuthConfig config, string uri, string session
/// </summary>
/// <param name="redirectUri"> Final uri that user lands on after completing sign in in browser. </param>
/// <param name="idToken"> Optional id token of an existing Firebase user. If set, it will effectivelly perform account linking. </param>
/// <param name="tenantId"> Optional tenant id.</param>
/// <returns></returns>
public async Task<UserCredential> ContinueSignInAsync(string redirectUri, string idToken = null)
public async Task<UserCredential> ContinueSignInAsync(string redirectUri, string idToken = null, string tenantId = null)
{
var (user, response) = await this.verifyAssertion.ExecuteAndParseAsync(
this.providerType,
Expand All @@ -44,11 +45,12 @@ public async Task<UserCredential> ContinueSignInAsync(string redirectUri, string
RequestUri = redirectUri,
SessionId = this.sessionId,
ReturnIdpCredential = true,
ReturnSecureToken = true
ReturnSecureToken = true,
TenantId = tenantId
}).ConfigureAwait(false);

var provider = this.config.GetAuthProvider(this.providerType) as OAuthProvider ?? throw new InvalidOperationException($"{this.providerType} is not a OAuthProvider");
var credential = provider.GetCredential(response);
var credential = provider.GetCredential(response, tenantId);

response.Validate(credential);

Expand Down
27 changes: 16 additions & 11 deletions src/Auth/Providers/OAuthProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ public OAuthProvider()

protected virtual string LocaleParameterName => null;

protected static AuthCredential GetCredential(FirebaseProviderType providerType, string accessToken, OAuthCredentialTokenType tokenType)
protected static AuthCredential GetCredential(FirebaseProviderType providerType, string accessToken, string tenantId, OAuthCredentialTokenType tokenType)
{
return new OAuthCredential
{
ProviderType = providerType,
Token = accessToken,
TokenType = tokenType
TokenType = tokenType,
TenantId = tenantId
};
}

Expand All @@ -50,15 +51,16 @@ public virtual FirebaseAuthProvider AddCustomParameters(params KeyValuePair<stri
return this;
}

internal virtual AuthCredential GetCredential(VerifyAssertionResponse response)
internal virtual AuthCredential GetCredential(VerifyAssertionResponse response, string tenantId)
{
return GetCredential(
this.ProviderType,
response.PendingToken ?? response.OauthAccessToken,
this.ProviderType,
response.PendingToken ?? response.OauthAccessToken,
tenantId,
response.PendingToken == null ? OAuthCredentialTokenType.AccessToken : OAuthCredentialTokenType.PendingToken);
}

internal virtual async Task<OAuthContinuation> SignInAsync()
internal virtual async Task<OAuthContinuation> SignInAsync(string tenantId = null)
{
if (this.LocaleParameterName != null && !this.parameters.ContainsKey(this.LocaleParameterName))
{
Expand All @@ -71,6 +73,7 @@ internal virtual async Task<OAuthContinuation> SignInAsync()
ProviderId = this.ProviderType,
CustomParameters = this.parameters,
OauthScope = this.GetParsedOauthScopes(),
TenantId = tenantId
};

var response = await this.createAuthUri.ExecuteAsync(request).ConfigureAwait(false);
Expand All @@ -87,10 +90,11 @@ protected internal override async Task<UserCredential> SignInWithCredentialAsync
PostBody = c.GetPostBodyValue(credential.ProviderType),
PendingToken = c.GetPendingTokenValue(),
ReturnIdpCredential = true,
ReturnSecureToken = true
ReturnSecureToken = true,
TenantId = credential.TenantId
}).ConfigureAwait(false);

credential = this.GetCredential(response);
credential = this.GetCredential(response, credential.TenantId);

response.Validate(credential);

Expand All @@ -107,14 +111,15 @@ protected internal override async Task<UserCredential> LinkWithCredentialAsync(s
PostBody = c.GetPostBodyValue(c.ProviderType),
PendingToken = c.GetPendingTokenValue(),
ReturnIdpCredential = true,
ReturnSecureToken = true
ReturnSecureToken = true,
TenantId = credential.TenantId
}).ConfigureAwait(false);

credential = this.GetCredential(response);
credential = this.GetCredential(response, credential.TenantId);

response.Validate(credential);

return new UserCredential(user, this.GetCredential(response), OperationType.Link);
return new UserCredential(user, credential, OperationType.Link);
}

protected string GetParsedOauthScopes()
Expand Down
Loading