Firebase 3:使用 .net 和 C# 创建自定义身份验证令牌
Firebase 3: creating a custom authentication token using .net and c#
我正在尝试使用自定义令牌实施 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 得到一个错误说:
Error authenticating Firebase user. Code: auth/invalid-custom-token Message: The custom token format is incorrect. Please check the documentation.
尝试添加不同的令牌过期控制声明也无济于事。
我还尝试使用 "dvsekhvalnov/jose-jwt" 库生成令牌,但无法使用 "RS256" 算法。
所以问题:
对我做错了什么有什么建议吗?
到目前为止还没有找到问题的直接答案,所以现在最终得到以下解决方案:
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);
});
也许有人会觉得这有帮助。
这个纯 .NET 解决方案适用于我,使用 Org.BouncyCastle (https://www.nuget.org/packages/BouncyCastle/) and Jose.JWT (https://www.nuget.org/packages/jose-jwt/) 库。
我遵循了这些步骤:
- 在 Firebase 控制台中,单击左上角项目名称旁边的 'cog' 图标,然后单击 'Permissions'。
- 在 IAM 和管理页面,单击左侧的 'Service Accounts'
- 点击上方的'Create Service Account',在selection中输入'Service Account Name'、select、'Project->Editor',勾选'Furnish a new private key'复选框和 select 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 中工作,我需要更改应用程序的池标识并将 "load user profile" 设置设置为 true。
@Elliveny 的回答对我很有效。我在 .NET Core 2.0 应用程序中使用它,并在已接受的答案的基础上将此解决方案转变为 class,可以在应用程序服务容器中注册为单例依赖项,并传入配置通过构造函数,以便我们可以利用 .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;
}
}
@Elliveny 的代码在本地对我有用,但在天蓝色中会抛出错误:"The system cannot find the file specified"。由于我稍微更改了代码,现在可以在两台服务器上使用了。
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;
}
我正在尝试使用自定义令牌实施 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 得到一个错误说:
Error authenticating Firebase user. Code: auth/invalid-custom-token Message: The custom token format is incorrect. Please check the documentation.
尝试添加不同的令牌过期控制声明也无济于事。
我还尝试使用 "dvsekhvalnov/jose-jwt" 库生成令牌,但无法使用 "RS256" 算法。
所以问题:
对我做错了什么有什么建议吗?
到目前为止还没有找到问题的直接答案,所以现在最终得到以下解决方案:
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);
});
也许有人会觉得这有帮助。
这个纯 .NET 解决方案适用于我,使用 Org.BouncyCastle (https://www.nuget.org/packages/BouncyCastle/) and Jose.JWT (https://www.nuget.org/packages/jose-jwt/) 库。
我遵循了这些步骤:
- 在 Firebase 控制台中,单击左上角项目名称旁边的 'cog' 图标,然后单击 'Permissions'。
- 在 IAM 和管理页面,单击左侧的 'Service Accounts'
- 点击上方的'Create Service Account',在selection中输入'Service Account Name'、select、'Project->Editor',勾选'Furnish a new private key'复选框和 select 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 中工作,我需要更改应用程序的池标识并将 "load user profile" 设置设置为 true。
@Elliveny 的回答对我很有效。我在 .NET Core 2.0 应用程序中使用它,并在已接受的答案的基础上将此解决方案转变为 class,可以在应用程序服务容器中注册为单例依赖项,并传入配置通过构造函数,以便我们可以利用 .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;
}
}
@Elliveny 的代码在本地对我有用,但在天蓝色中会抛出错误:"The system cannot find the file specified"。由于我稍微更改了代码,现在可以在两台服务器上使用了。
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;
}