首页 文章

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

提问于
浏览
13

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

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

所以根据说明(https://firebase.google.com/docs/server/setup)我've created a service account for my Firebase application and generated a key in ' .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”算法 .

所以问题是:

Any suggestion on what am I doing wrong?

4 回答

  • 14

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

    我按照以下步骤操作:

    • 在Firebase控制台中,单击项目名称旁边左上角的'cog'图标,然后单击'Permissions' .

    • 在IAM和管理员页面,单击左侧的'Service Accounts'

    • 单击顶部的'Create Service Account',输入'Service Account Name',在角色选择中选择'Project->Editor',勾选'Furnish a new private key'复选框并选择JSON

    • 单击'Create'并下载服务帐户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 .

  • 1

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

    Using instruction here使用服务帐户详细信息生成了一个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);
    });
    

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

  • 2

    @ 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应用程序中使用它,并 Build 在已接受的答案上,将此解决方案转换为可在app services容器中注册为singleton依赖项的类,以及通过构造函数传递的配置,以便我们可以将.NET秘密用于本地开发配置和环境变量以进行 生产环境 配置 .

    我还整理了一下流处理 .

    Note for .NET Core devs - 你需要使用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;
        }
    }
    

相关问题