From a5849dfa6eac6c1a87f2cf072f261fa73e9bcfb3 Mon Sep 17 00:00:00 2001 From: Sjoerd van Noort Date: Wed, 11 Oct 2023 20:18:44 +0200 Subject: [PATCH] Custom Token signin support https://firebase.google.com/docs/auth/admin/create-custom-tokens --- src/Auth/FirebaseAuthClient.cs | 12 +++ src/Auth/FirebaseProviderType.cs | 5 +- src/Auth/Providers/CustomTokenProvider.cs | 89 ++++++++++++++++++++++ src/Auth/Requests/Endpoints.cs | 8 +- src/Auth/Requests/SignInWithCustomToken.cs | 31 ++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/Auth/Providers/CustomTokenProvider.cs create mode 100644 src/Auth/Requests/SignInWithCustomToken.cs diff --git a/src/Auth/FirebaseAuthClient.cs b/src/Auth/FirebaseAuthClient.cs index 0ea5b9d..b6d495d 100644 --- a/src/Auth/FirebaseAuthClient.cs +++ b/src/Auth/FirebaseAuthClient.cs @@ -159,6 +159,18 @@ public async Task FetchSignInMethodsForEmailAsync(stri return new FetchUserProvidersResult(email, response.Registered, response.SigninMethods, response.AllProviders); } + public async Task SignInWithCustomTokendAsync(string customToken) + { + await this.CheckAuthDomain().ConfigureAwait(false); + + var provider = (CustomTokenProvider)this.config.GetAuthProvider(FirebaseProviderType.CustomToken); + var result = await provider.SignInUserAsync(customToken).ConfigureAwait(false); + + this.SaveToken(result.User); + + return result; + } + public async Task SignInWithEmailAndPasswordAsync(string email, string password) { await this.CheckAuthDomain().ConfigureAwait(false); diff --git a/src/Auth/FirebaseProviderType.cs b/src/Auth/FirebaseProviderType.cs index 7f4e0ab..8ee0a07 100644 --- a/src/Auth/FirebaseProviderType.cs +++ b/src/Auth/FirebaseProviderType.cs @@ -33,6 +33,9 @@ public enum FirebaseProviderType [EnumMember(Value = "phone")] Phone, - Anonymous + [EnumMember(Value = "customtoken")] + CustomToken, + + Anonymous } } diff --git a/src/Auth/Providers/CustomTokenProvider.cs b/src/Auth/Providers/CustomTokenProvider.cs new file mode 100644 index 0000000..e46880f --- /dev/null +++ b/src/Auth/Providers/CustomTokenProvider.cs @@ -0,0 +1,89 @@ +using Firebase.Auth.Requests; +using System.Linq; +using System.Threading.Tasks; +using static Firebase.Auth.Providers.EmailProvider; + +namespace Firebase.Auth.Providers +{ + public class CustomTokenProvider : FirebaseAuthProvider + { + private SignInWithCustomToken signInWithCustomToken; + private GetAccountInfo getAccountInfo; + + internal override void Initialize(FirebaseAuthConfig config) + { + base.Initialize(config); + + this.signInWithCustomToken = new SignInWithCustomToken(config); + this.getAccountInfo = new GetAccountInfo(this.config); + } + + public static AuthCredential GetCredential(string customToken) + { + return new CustomTokenCredential + { + ProviderType = FirebaseProviderType.CustomToken, + CustomToken = customToken + }; + } + + public Task SignInUserAsync(string customToken) + { + return this.SignInWithCredentialAsync(GetCredential(customToken)); + } + + public override FirebaseProviderType ProviderType => FirebaseProviderType.CustomToken; + + protected internal override async Task SignInWithCredentialAsync(AuthCredential credential) + { + var ec = (CustomTokenCredential)credential; + + var response = await this.signInWithCustomToken.ExecuteAsync(new SignInWithCustomTokenRequest + { + Token = ec.CustomToken, + ReturnSecureToken = true + }).ConfigureAwait(false); + + var user = await this.GetUserInfoAsync(response.IdToken).ConfigureAwait(false); + var fc = new FirebaseCredential + { + ExpiresIn = response.ExpiresIn, + IdToken = response.IdToken, + RefreshToken = response.RefreshToken, + ProviderType = FirebaseProviderType.EmailAndPassword + }; + + return new UserCredential(new User(this.config, user, fc), ec, OperationType.SignIn); + } + + protected internal override Task LinkWithCredentialAsync(string idToken, AuthCredential credential) + { + // Anonnymouse accounts or relinking not supported with this method + throw new System.NotSupportedException(); + } + + private async Task GetUserInfoAsync(string idToken) + { + var getResponse = await this.getAccountInfo.ExecuteAsync(new IdTokenRequest { IdToken = idToken }).ConfigureAwait(false); + var user = getResponse.Users[0]; + + return new UserInfo + { + DisplayName = user.DisplayName, + Email = user.Email, + IsEmailVerified = user.EmailVerified, + Uid = user.LocalId, + PhotoUrl = user.PhotoUrl, + IsAnonymous = false + }; + } + + internal class CustomTokenCredential : AuthCredential + { + public string CustomToken { get; set; } + } + } + + + +} diff --git a/src/Auth/Requests/Endpoints.cs b/src/Auth/Requests/Endpoints.cs index 2ce4b4c..11f3c66 100644 --- a/src/Auth/Requests/Endpoints.cs +++ b/src/Auth/Requests/Endpoints.cs @@ -14,5 +14,9 @@ internal static class Endpoints public const string GoogleCreateAuthUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key={0}"; public const string GoogleProjectConfighUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getProjectConfig?key={0}"; public const string GoogleUpdateUserPassword = "https://identitytoolkit.googleapis.com/v1/accounts:update?key={0}"; - } -} \ No newline at end of file + public const string GoogleSignInWithCustomToken = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key={0}"; + + + + } +} diff --git a/src/Auth/Requests/SignInWithCustomToken.cs b/src/Auth/Requests/SignInWithCustomToken.cs new file mode 100644 index 0000000..dbf7fba --- /dev/null +++ b/src/Auth/Requests/SignInWithCustomToken.cs @@ -0,0 +1,31 @@ +namespace Firebase.Auth.Requests +{ + public class SignInWithCustomTokenRequest + { + public string Token { get; set; } + + public bool ReturnSecureToken { get; set; } + } + + public class SignInWithCustomTokenResponse + { + public string IdToken { get; set; } + + public string RefreshToken { get; set; } + + public int ExpiresIn { get; set; } + } + + /// + /// Uses a custom token generated in a different backend (server) proces to login to firebase. + /// see: https://firebase.google.com/docs/auth/admin/create-custom-tokens + /// + public class SignInWithCustomToken : FirebaseRequestBase + { + public SignInWithCustomToken(FirebaseAuthConfig config) : base(config) + { + } + + protected override string UrlFormat => Endpoints.GoogleSignInWithCustomToken; + } +}