Firebase 3:使用.net和c#创建自定义身份validation令牌

我正在尝试使用自定义令牌实施Firebase 3身份validation机制(如https:// firebase.google.com/docs/auth/server/create-custom-tokens中所述)。

我的服务器是ASP.NET MVC Application。

因此,根据说明( https://firebase.google.com/docs/server/setup ),我为Firebase应用程序创建了一个服务帐户,并以“.p12”格式生成了一个密钥。

之后根据此处的说明( https://firebase.google.com/docs/auth/server/create-custom-tokens#create_custom_tokens_using_a_third-party_jwt_library )我尝试生成自定义令牌并使用之前收到的密钥对其进行签名步。 对于令牌生成,我使用了Microsoft的SystemIdentityModel.Tokens.Jwt库,因此代码如下所示:

var now = DateTime.UtcNow; var tokenHandler = new JwtSecurityTokenHandler(); var key = new X509AsymmetricSecurityKey(new X509Certificate2(p12path, p12pwd)); var signinCredentials = new SigningCredentials(key, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmlenc#rsa-sha256"); Int32 nowInUnixTimestamp = (Int32)(now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; var token = tokenHandler.CreateToken( issuer: serviceAccountEmail, audience: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit", signingCredentials: signinCredentials, subject: new ClaimsIdentity(new Claim[] { new Claim("sub", serviceAccountEmail), new Claim("iat", nowInUnixTimestamp.ToString()), new Claim("exp", (nowInUnixTimestamp + (60*60)).ToString()), new Claim("uid", uid) }) ); var tokenString = tokenHandler.WriteToken(token); 

然后尝试使用Firebase Javascript SDK在React Native应用程序中登录用户,使用以下代码:

 //omitting initialization code firebase.auth().signInWithCustomToken(firebaseJWT).catch(function(error) { console.log('Error authenticating Firebase user. Code: ' + error.code + ' Message: ' + error.message); }); 

但Firebase的错误说:

validationFirebase用户时出错。 代码:auth / invalid-custom-token消息:自定义标记格式不正确。 请查看文档。

尝试为令牌到期控制添加不同的声明也没有帮助。

此外,我尝试使用“dvsekhvalnov / jose-jwt”库生成令牌,但无法使用“RS256”算法。

所以问题是:

关于我做错了什么的任何建议?

这个纯.NET解决方案适用于我,使用Org.BouncyCastle( https://www.nuget.org/packages/BouncyCastle/ )和Jose.JWT( https://www.nuget.org/packages/jose-jwt / )库。

我按照以下步骤操作:

  • 在Firebase控制台中,单击项目名称旁边左上角的“cog”图标,然后单击“权限”。
  • 在IAM和管理员页面,单击左侧的“服务帐户”
  • 点击顶部的“创建服务帐户”,输入“服务帐户名称”,在角色选择中选择“项目 – >编辑”,勾选“提供新的私钥”复选框,然后选择JSON
  • 单击“创建”并下载服务帐户JSON文件并确保其安全。
  • 在合适的文本编辑器中打开服务帐户JSON文件,并将值放入以下代码中:

     // private_key from the Service Account JSON file public static string firebasePrivateKey=@"-----BEGIN PRIVATE KEY-----\nMIIE...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n-----END PRIVATE KEY-----\n"; // Same for everyone public static string firebasePayloadAUD="https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"; // client_email from the Service Account JSON file public static string firebasePayloadISS="serviceaccountname@projectname.iam.gserviceaccount.com"; public static string firebasePayloadSUB="serviceaccountname@projectname.iam.gserviceaccount.com"; // the token 'exp' - max 3600 seconds - see https://firebase.google.com/docs/auth/server/create-custom-tokens public static int firebaseTokenExpirySecs=3600; private static RsaPrivateCrtKeyParameters _rsaParams; private static object _rsaParamsLocker=new object(); void Main() { // Example with custom claims var uid="myuserid"; var claims=new Dictionary { {"premium_account", true} }; Console.WriteLine(EncodeToken(uid, claims)); } public static string EncodeToken(string uid, Dictionary claims) { // Get the RsaPrivateCrtKeyParameters if we haven't already determined them if (_rsaParams == null) { lock (_rsaParamsLocker) { if (_rsaParams == null) { StreamReader sr = new StreamReader(GenerateStreamFromString(firebasePrivateKey.Replace(@"\n","\n"))); var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); } } } var payload = new Dictionary { {"claims", claims} ,{"uid", uid} ,{"iat", secondsSinceEpoch(DateTime.UtcNow)} ,{"exp", secondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} ,{"aud", firebasePayloadAUD} ,{"iss", firebasePayloadISS} ,{"sub", firebasePayloadSUB} }; return Jose.JWT.Encode(payload, Org.BouncyCastle.Security.DotNetUtilities.ToRSA(_rsaParams), JwsAlgorithm.RS256); } private static long secondsSinceEpoch(DateTime dt) { TimeSpan t = dt - new DateTime(1970, 1, 1); return (long)t.TotalSeconds; } private static Stream GenerateStreamFromString(string s) { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); stream.Position = 0; return stream; } 

为了在IIS中工作,我需要更改应用程序的池标识并将“加载用户配置文件”设置为true。

到目前为止还没有找到问题的直接答案,所以现在最终得到以下解决方案:

使用此处的说明生成带有服务帐户详细信息的JSON文件,并使用Firebase服务器SDK创建基本Node.js服务器,该服务器SDK使用以下代码为Firebase生成正确的自定义令牌:

 var http = require('http'); var httpdispatcher = require('httpdispatcher'); var firebase = require('firebase'); var config = { serviceAccount: { projectId: "{projectId}", clientEmail: "{projectServiceEmail}", privateKey: "-----BEGIN PRIVATE KEY----- ... ---END PRIVATE KEY-----\n" }, databaseURL: "https://{projectId}.firebaseio.com" }; firebase.initializeApp(config); const PORT=8080; httpdispatcher.onGet("/firebaseCustomToken", function(req, res) { var uid = req.params.uid; if (uid) { var customToken = firebase.auth().createCustomToken(uid); res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({'firebaseJWT' : customToken})); } else { res.writeHead(400, {'Content-Type': 'text/plain'}); res.end('No uid parameter specified'); } }); function handleRequest(request, response){ try { //log the request on console console.log(request.url); //Disptach httpdispatcher.dispatch(request, response); } catch(err) { console.log(err); } } //create a server var server = http.createServer(handleRequest); //start our server server.listen(PORT, function(){ console.log("Server listening on: http://localhost:%s", PORT); }); 

也许有人会觉得这很有帮助。

@ Elliveny的代码在本地工作,但在azure中抛出一个错误:“系统找不到指定的文件”。 由于我已经更改了一点代码,现在可以在两个服务器上运行。

 private string EncodeToken(string uid, Dictionary claims) { string jwt = string.Empty; RsaPrivateCrtKeyParameters _rsaParams; using (StreamReader sr = new StreamReader(GenerateStreamFromString(private_key.Replace(@"\n", "\n")))) { var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); } using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { Dictionary payload = new Dictionary { {"claims", claims} ,{"uid", uid} ,{"iat", secondsSinceEpoch(DateTime.UtcNow)} ,{"exp", secondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} ,{"aud", firebasePayloadAUD} ,{"iss", client_email} ,{"sub", client_email} }; RSAParameters rsaParams = DotNetUtilities.ToRSAParameters(_rsaParams); rsa.ImportParameters(rsaParams); jwt = JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256); } return jwt; } 

@ Elliveny的回答对我很有帮助。 我在.NET Core 2.0应用程序中使用它,并建立在已接受的答案上,将此解决方案转换为可在app services容器中注册为singleton依赖项的类,以及通过构造函数传递的配置,以便我们可以将.NET秘密用于本地开发配置和环境变量以进行生产配置。

我还整理了一下流处理。

.NET Core开发人员的注意事项 – 您需要使用Portable.BouncyCastle

您可以通过使用Jwt.IO解析输出JWT标记来测试编码结果

 using Jose; using Org.BouncyCastle.Crypto.Parameters; using System; using System.Collections.Generic; using System.IO; using System.Linq; public class FirebaseTokenGenerator { // private_key from the Service Account JSON file public static string firebasePrivateKey; // Same for everyone public static string firebasePayloadAUD = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"; // client_email from the Service Account JSON file public static string firebasePayloadISS; public static string firebasePayloadSUB; // the token 'exp' - max 3600 seconds - see https://firebase.google.com/docs/auth/server/create-custom-tokens public static int firebaseTokenExpirySecs = 3600; private static RsaPrivateCrtKeyParameters _rsaParams; private static object _rsaParamsLocker = new object(); public FirebaseTokenGenerator(string privateKey, string clientEmail) { firebasePrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey)); firebasePayloadISS = clientEmail ?? throw new ArgumentNullException(nameof(clientEmail)); firebasePayloadSUB = clientEmail; } public static string EncodeToken(string uid) { return EncodeToken(uid, null); } public static string EncodeToken(string uid, Dictionary claims) { // Get the RsaPrivateCrtKeyParameters if we haven't already determined them if (_rsaParams == null) { lock (_rsaParamsLocker) { if (_rsaParams == null) { using (var streamWriter = WriteToStreamWithString(firebasePrivateKey.Replace(@"\n", "\n"))) { using (var sr = new StreamReader(streamWriter.BaseStream)) { var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); } } } } } var payload = new Dictionary { {"uid", uid} ,{"iat", SecondsSinceEpoch(DateTime.UtcNow)} ,{"exp", SecondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} ,{"aud", firebasePayloadAUD} ,{"iss", firebasePayloadISS} ,{"sub", firebasePayloadSUB} }; if (claims != null && claims.Any()) { payload.Add("claims", claims); } return JWT.Encode(payload, Org.BouncyCastle.Security.DotNetUtilities.ToRSA(_rsaParams), JwsAlgorithm.RS256); } private static long SecondsSinceEpoch(DateTime dt) { TimeSpan t = dt - new DateTime(1970, 1, 1); return (long) t.TotalSeconds; } private static StreamWriter WriteToStreamWithString(string s) { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); stream.Position = 0; return writer; } }