【问题标题】:Firebase 3: creating a custom authentication token using .net and c#Firebase 3:使用 .net 和 c# 创建自定义身份验证令牌
【发布时间】:2016-11-06 09:17:10
【问题描述】:

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

我的服务器是 ASP.NET MVC 应用程序。

因此,根据说明 (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 收到错误消息:

对 Firebase 用户进行身份验证时出错。代码:auth/invalid-custom-token 消息:自定义令牌格式不正确。请检查文档。

尝试为令牌过期控制添加不同的声明也无济于事。

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

那么问题来了:

关于我做错了什么有什么建议吗?

【问题讨论】:

  • 我意识到链接stackoverflow.com/questions/37408684/…描述的token格式是Firebase自己发布的token,所以第一个问题不再是问题了。
  • 这是 Google 支持人员对同一问题的回答:“我在 SO 中看到您的帖子,您已经有了解决方法。对于令牌格式,您应该始终关注最新消息最新的文档。目前在身份验证方面存在一些问题,我们正在尽最大努力让事情顺利进行。请留意我们的发行说明以获取任何进一步的更新,并随时与我们联系如果需要。” 因此,目前看来解决方法是最好的选择。

标签: c# asp.net-mvc firebase firebase-authentication


【解决方案1】:

@Elliveny 的代码在本地为我工作,但在 azure 中会引发错误:“系统找不到指定的文件”。由于我对代码进行了一些更改,现在可以在两个服务器上运行。

private string EncodeToken(string uid, Dictionary<string, object> 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<string, object> payload = new Dictionary<string, object> {
                {"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;

    }

【讨论】:

    【解决方案2】:

    @Elliveny 的回答对我很有帮助。我在 .NET Core 2.0 应用程序中使用它,并基于已接受的答案将此解决方案转换为可以在应用程序服务容器中注册为单例依赖项的类,并通过构造函数传入配置,以便我们可以利用 .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<string, object> 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<string, object> {
            {"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;
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      我按照以下步骤操作:

      • 在 Firebase 控制台中,点击左上角项目名称旁边的“cog”图标,然后点击“Permissions”。
      • 在 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<string, object> {
                {"premium_account", true}
            };
            Console.WriteLine(EncodeToken(uid, claims));
        }
        
        public static string EncodeToken(string uid, Dictionary<string, object> 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<string, object> {
                {"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。

      【讨论】:

      • 您好,我已经尝试过您的代码。当我在jwt.io 检查生成的令牌时,它说签名无效。
      • @AjayPunekar 代码仍在为我工作。你解决了吗?
      • @Elliveny 我尝试了代码,生成了令牌,但是当我将其发送到 Firebase 时收到“自定义令牌格式不正确”错误消息,知道吗?
      • @RezaRahmati 如果没有看到您的代码,我会情不自禁。也许提出一个关于细节的新问题?
      • @Elliveny 我复制粘贴你的代码,它完全相同,只是变量值不同
      【解决方案4】:

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

      Using instruction here 生成了一个包含服务帐户详细信息的 JSON 文件,并使用 Firebase 服务器 SDK 创建了一个基本的 Node.js 服务器,它使用以下代码为 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);
      });
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-10-25
        • 1970-01-01
        • 2016-11-06
        • 2023-03-12
        • 2019-07-04
        • 2017-07-03
        • 1970-01-01
        • 2017-12-19
        相关资源
        最近更新 更多