I am using C# asp.net
I want to use jwt token for my webpage.
So whenever the page loads , i have to use jwt , im a beginner so i dont have much idea, i know how it works, but i dont know where to start from or how to implement exactly.
i have a login page and i only need jwt for "online id/admin/username".
Using these
SymmetricSecurityKey
SigningCredentials
JwtHeader
JwtPayload
JwtSecurityToken
JwtSecurityTokenHandler
WriteToken
var token = handler.ReadJwtToken(tokenString);
and googling gives my result for .net core which is not what i want,
can someone help me?
Thankyou
I tried some code snippets but im sure im not doing it the right way
To authenticate using JWT, you must first register the user and store it in your database. When logging in and validating the user with database information, use the following code to create a JWT token.
public static string GenerateJwtToken()
{
DateTime value = DateTime.Now.AddMinutes(20.0);
byte[] bytes = Encoding.ASCII.GetBytes("MIIBrTCCAaGg ...");
SigningCredentials signingCredentials = new SigningCredentials(new SymmetricSecurityKey(bytes), "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256");
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Expires = value,
SigningCredentials = signingCredentials
};
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = jwtSecurityTokenHandler.CreateToken(tokenDescriptor);
return jwtSecurityTokenHandler.WriteToken(token);
}
Then, in the actions that have the Authorize attribute, you must send the token created above in the request header.
[HttpPost]
[Authorize]
public async Task<IActionResult> Test(TestRequest input)
{
.
.
.
}
I wrote a simple example, you can see the complete implementation example with JWT from this link
Related
I wanted to ask for advice for a specific approach using ApiKey and JWT token authentication. My .NET 6 application is like a middleware service, which gets a request from other service through HTTP. After that request, I am checking whether it has JWT token in Headers and if it has, I use it to validate the token. After validating it, I need to pass it to my other service class under the same project, there I create a HttpClient and I want to put that JWT token into it's header section. Unfortunately, I am unable to pass it through. I tried creating 'JwtTokenStore' class, store a JWT there and then pass it with dependency injection. I used AddTransient, but then realized that I got a 'null' value in my service, because it creates another instance of that 'IJwtTokenStore'.
I will give you some code snippets:
ApiKeyHandler:
protected override async Task<AuthenticateResult> HandleAuthenticationAsync()
{
if (Request.Headers.TryGetValue(ApiKeyAuthenticationOptions.ApiKeyHeaderName, out var apiKeyHeaderValues))
{
headerKey = apiKeyHeaderValues.ToArray().FirstOrDefault();
if (!string.IsNullOrEmpty(validKey) && !validKey.Equals(headerKey) && !validKey.Equals(uriKey))
{
return AuthenticateResult.NoResult();
}
}
else
{
// check for JWT token
var jwt = Request.Headers["Authorization"].FirstOrDefault(x => x.StartsWith("Bearer "));
if (string.IsNullOrEmpty(jwt))
return AuthenticateResult.Fail("No ApiKey or JWT token present in request headers.");
// validate JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
try
{
var jwtToken = tokenHandler.ReadJwtToken(jwt[7..]);
var expClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp)?.Value;
validationParameters.ValidateLifetime = !string.IsNullOrEmpty(expClaim);
tokenHandler.ValidateToken(jwt[7..], validationParameters, out SecurityToken validatedToken);
// Maybe here I should store somewhere my JWT token if it's valid
}
catch
{
return AuthenticateResult.NoResult();
}
}
}
My custom service constructor where I want to add the JWT token if it's valid:
public CustomService(ILogger<CustomService> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_apiKey = configuration.GetValue<string>("ApiKey");
_httpClient = httpClientFactory?.CreateClient() ?? new HttpClient();
// here I should get that JWT token and check if it's not null, if null
// then I use ApiKey
if (!isApiKey || !string.IsNullOrEmpty(jwt))
{
_httpClient.DefaultRequestHeaders.Add("Authorization", jwt);
}
else //(!string.IsNullOrEmpty(_apiKey))
{
_httpClient.DefaultRequestHeaders.Add("X-Api-Key", _apiKey);
}
}
Can you help me advicing how should I achieve this solution? Don't forget that this middleware application will be getting a lot of requests at the same time, so I need to know which JWT token should I send to my 3rd party service.
All the answers appreciated!
I've tried creating 'JwtTokenStore', put a JWT there and then try to get it in my custom service. After failing (because of AddTransient), I tried creating 'TokenQueue' class with ConcurrentQueue<string> and store the JWT there. But after sending two requests at the same time, in my 3rd party application it only receives the same token.
I saw an answer in this question: Can API Key and JWT Token be used in the same .Net 6 WebAPI but I also stuck at sending that JWT token forward to my custom service.
I also thought about a solution with Dictionary, that I should put token with some user's name or whatsoever, and then getting a token by that. But I am not sure if it's the best solution.
First some code... I have a Security class:
public static class Security
{
public static RSACryptoServiceProvider RSA { get; } = new(4096);
public static SigningCredentials Credentials()
{
return new SigningCredentials(new RsaSecurityKey(RSA), SecurityAlgorithms.RsaSha512)
{
CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
};
}
}
This is a simple static class that generates an RSA key and related signing credentials. It has some more code but that's not important for my question...
Then I have code to generate a JWT token using System.IdentityModel.Tokens.Jwt and System.Security.Claims:
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Jti, account.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, account.Name),
new Claim(JwtRegisteredClaimNames.Name, account.FullName),
new Claim(JwtRegisteredClaimNames.GivenName, account.FirstName),
new Claim(JwtRegisteredClaimNames.FamilyName, account.LastName),
new Claim(JwtRegisteredClaimNames.Birthdate, account.Birthdate.ToString()),
new Claim(JwtRegisteredClaimNames.Email, account.Email.ToString()),
};
var token = new JwtSecurityToken
(
issuer: host,
audience: audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddHours(4),
Security.Credentials()
);
Again, not too challenging. A few claims from data in my code, which is my own class with basic user data. And this is turned into a token that is signed. It results in this token:
eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhYmQ3NjUxMS1jNmNmLTQzZjUtOGE1Zi1iZjEzZTg0NGM1ZmEiLCJzdWIiOiJ3aW0iLCJuYW1lIjoiV2ltIFRlbiBCcmluayIsImdpdmVuX25hbWUiOiJXaW0iLCJmYW1pbHlfbmFtZSI6IlRlbiBCcmluayIsImJpcnRoZGF0ZSI6IjExLTctMTk2NiIsImVtYWlsIjoid2ltQGV4YW1wbGUuY29tIiwibmJmIjoxNjQwNjU1MDA1LCJleHAiOjE2NDA2Njk0MDUsImlzcyI6ImxvY2FsaG9zdDo3MTkwIiwiYXVkIjoiL1Rva2VuL0xvZ2luIn0.iFn6DKvHxW_Vd6bxlLs4quQwCyYWpL7bbyMJSTVMxS4RRQ9GxM9IvgoymEqyn9YNBc68TFULNPRVBFE8_mF-IpYt1NIGtp8p7u9laMRegEfXyvJFislDcPZoMbD5xjB18xwUwTpkNjdX4svzFIR8mQT5bLcb5BojFLhxUVT-oJ8G_f0lD0XWJkDo40OW9aHECjIOOu_KfeYZiPdiuo-q9yed6WN1-dgqz4ykWOYC7FoMA0ZYogG5pRvmVLOq8haiEDEJRszbhjj6CLt6h_hlwOS0mMZF6a7Ag5mN4lI2UtEot-jYyXM72eXgBPm_bWNsa6RG_m9vFf4EElK4ekfFd3V_qkAnZ8XEhfTcRD8ASEVjXg8hTI5SY4LFyrggso44HFN5oEoVWlVVcGZxEG2R6LspJSFD4xkrVUUeQvt7IbBL1ViflSfGYp6-cLMPfxo3qZvItC7DxP_8aAygNmHi0_T8BxGNLnOwqrPtDOjKzw4SqEEwBf5S5R6O_dufhauAVvvS_rKQnkEdq-MNNNW5U90ytauCcAgFEGa03MN3E4hhQKUE5y70wCStrIFcBtJezf32U8V7SHv1WrVMpnQXxQoLneZh3b7QsthKCh49ypUSLjw4yL9VVur2K0oV8NsLnwoqH5dCVFuq7YHmreiTa-ZUIFVrgWYMzMqR8hG7Ato
And now I want to convert it back to a token by using this code:
var tokenData = new JwtSecurityToken(tokenString);
And that will generate the token from the string again, but it's not verified, of course. And I need to verify the signature for this token.
To be clear: this is NOT for an ASP.NET Core application but I do use .NET Core for this console application. This is a simple tool that connects to a database which contains tokens as strings and it just has to validate every token inside it with a specific RSA key. (Also stored in a safe location.) Tokens in this database come from a web application which uses this table for logging purposes but apparently some tokens get signed with a wrong or invalid key and I need a quick way to find which tokens are invalid in this log.
So, what is the easiest way to validate these signatures?
And an update: My code is using Microsoft.AspNetCore.Authentication.JwtBearer and for some reason it caused some unexpected behavior and errors during validations. When I kicked it out and replaced it with System.IdentityModel.Tokens.Jwt it all starts to work.
Both packages are very similar in classes and other parts, and I'm not sure how they differ from one another, but they do differ. One is to work with OpenID and the other one for JWT in general. Switching to the last one solved my problems...
As it turns out, it seems to be a bug in package Microsoft.AspNetCore.Authentication.JwtBearer but the package System.IdentityModel.Tokens.Jwt is near-identical but got the job done. The following code just works:
var param = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromMinutes(5),
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateTokenReplay = false,
ValidateLifetime = true,
IssuerSigningKey = new RsaSecurityKey(Security.RSA),
ValidAudience = Audience,
ValidIssuer = Issuer,
};
var claim = new JwtSecurityTokenHandler().ValidateToken(tokenString, param, out _);
if (claim == null) throw new InvalidSignature(tokenString);
(But with the Bearer library ir failed. Not sure why.)
If you're not limited to the language, you can use a library that we created at Curity for validating JWTs: https://www.npmjs.com/package/#curity/jwt-validation. We've also created a list of libraries which you can use to validate JWTs: https://curity.io/resources/guides/libraries/api/ so if you want to stick to .NET you can find a library there. We also have a tutorial which shows how to use that .NET lib: https://curity.io/resources/learn/dotnet-api/
I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt for my .NET Core project.
Whenever I generate a new token I store that to the database. First of all this is how I generate a new token
public string GenerateToken(Dictionary<string, object> payload)
{
DateTime tokenExpiresAt = DateTime.Now.AddMilliseconds(1); // from config
byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Claims = payload,
Expires = tokenExpiresAt,
SigningCredentials = new SigningCredentials(symmetricSecurityKey,
SecurityAlgorithms.HmacSha256Signature)
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
string token = tokenHandler.WriteToken(securityToken);
return token;
}
The generated sample token is
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI
If a client tries to access a protected endpoint the default configuration will handle the basic validation (configured in the DI setup in the Startup file)
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = symmetricSecurityKey,
};
jwtBearerOptions.Events = new JwtBearerEvents()
{
OnTokenValidated = ProcessAfterTokenValidation
};
});
As you can see I added a method to the OnTokenValidated event listener. This method should check, if the token exists in the database.
public async Task ProcessAfterTokenValidation(TokenValidatedContext tokenValidatedContext)
{
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
string token = jwtSecurityTokenHandler.WriteToken(tokenValidatedContext.SecurityToken);
// ... check if token exists in db ...
}
The problem with that method is that the generated token is not the exact token as it is stored in the database. There is missing a last segment. The token variable holds this
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwibmJmIjoxNTk1NDQ1NjMxLCJleHAiOjE1OTU0NDU2OTEsImlhdCI6MTU5NTQ0NTYzMX0.
and the missing last part would be
cWvSpKC_yYao2_ziW_ahjjHpUl2SgUZvCmsjXntxCOI
Does someone know why the last part of the token is missing?
Thanks for help!
The last part of the token is the signature. The purpose of the OnTokenValidated method is to provide a ClaimsIdentity. The signature is not a component of the claims of the token holder.
If you had a key rotation policy on the issuing side a given user could present identical claims with a different signature before and after a key rotation.
The identity of the user is derived from a combination of the issuer plus any claims that identify the user (e.g. username in the token from your example).
In your case, since you are the issuer, the token minus the signature simply represents proof that the user has successfully authenticated against your token issuing endpoint. The claims within the token should lead to a database record rather than the token itself.
I have a Web API with tons of methods that all require a bearer token to be present in order to be used. These methods all extract information from the bearer token.
I want to test whether the API is properly populating the bearer token upon its generation. I'm using the Microsoft.Owin.Testing framework to write my tests. I have a test that looks like this:
[TestMethod]
public async Task test_Login()
{
using (var server = TestServer.Create<Startup>())
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");
req.And(x => x.Content = new StringContent("grant_type=password&username=test&password=1234", System.Text.Encoding.ASCII));
var response = await req.GetAsync();
// Did the request produce a 200 OK response?
Assert.AreEqual(response.StatusCode, System.Net.HttpStatusCode.OK);
// Retrieve the content of the response
string responseBody = await response.Content.ReadAsStringAsync();
// this uses a custom method for deserializing JSON to a dictionary of objects using JSON.NET
Dictionary<string, object> responseData = deserializeToDictionary(responseBody);
// Did the response come with an access token?
Assert.IsTrue(responseData.ContainsKey("access_token"));
}
}
So I'm able to retrieve the string that represents the token. But now I want to actually access that token's contents, and make sure that certain claims were provided.
Code that I would use in an actual authenticated method to check the claims looks like this:
var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;
var claimTypes = from x in claims select x.Type;
if (!claimTypes.Contains("customData"))
throw new InvalidOperationException("Not authorized");
So what I want to be able to do is, within my test itself, provide the bearer token string and reeceive a User.Identity object or in some other way gain access to the claims that token contains. This is how I want to test whether my method is properly adding the necessary claims to the token.
The "naive" approach could be to write a method in my API that simply returns all the claims in the bearer token it is given. But it feels like this should be unnecessary. ASP.NET is somehow decoding the given token to an object before my controller's method is called. I want to replicate the same action on my own, in my test code.
Can this be done? If so, how?
EDIT: My OWIN startup class instantiates an authentication token provider that I have coded which handles authentication and token generation. In my startup class I have this:
public void Configuration(IAppBuilder app)
{
// Setup configuration object
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// configure the OAUTH server
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//AllowInsecureHttp = false,
AllowInsecureHttp = true, // THIS HAS TO BE CHANGED BEFORE PUBLISHING!
TokenEndpointPath = new PathString("/authtoken"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new API.Middleware.MyOAuthProvider()
};
// Now we setup the actual OWIN pipeline.
// setup CORS support
// in production we will only allow from the correct URLs.
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
// insert actual web API and we're off!
app.UseWebApi(config);
}
Here is the relevant code from my OAuth provider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Will be used near end of function
bool isValidUser = false;
// Simple sanity check: all usernames must begin with a lowercase character
Match testCheck = Regex.Match(context.UserName, "^[a-z]{1}.+$");
if (testCheck.Success==false)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}
string userExtraInfo;
// Here we check the database for a valid user.
// If the user is valid, isValidUser will be set to True.
// Invalid authentications will return null from the method below.
userExtraInfo = DBAccess.getUserInfo(context.UserName, context.Password);
if (userExtraInfo != null) isValidUser = true;
if (!isValidUser)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}
// The database validated the user. We will include the username in the token.
string userName = context.UserName;
// generate a claims object
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// add the username to the token
identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
// add the custom data on the user to the token.
identity.AddClaim(new Claim("customData", userExtraInfo));
// store token expiry so the consumer can determine expiration time
DateTime expiresAt = DateTime.Now.Add(context.Options.AccessTokenExpireTimeSpan);
identity.AddClaim(new Claim("expiry", expiresAt.ToString()));
// Validate the request and generate a token.
context.Validated(identity);
}
The unit test would want to ensure that the customData claim is in fact present in the authentication token. So thus my need for a way to evaluate the token provided to test which claims it contains.
EDIT 2: I've spent some time looking over the Katana source code and searching out some other posts online, and it looks like it's important that I'm hosting this app on IIS, so I would be using SystemWeb. It looks like SystemWeb uses Machine Key encryption for the token. It also looks like the AccessTokenFormat parameter in the options is relevant here.
So now what I'm wondering is if I can instantiate my own "decoder" based on this knowledge. Assuming I will only ever be hosting on IIS, can I instantiate a decoder that can then decode the token and convert it into a Claims object?
The docs on this are kind of sparse and the code seems to throw you all over the place, a lot to try to keep straight in my head.
EDIT 3: I found a project that contains what is supposed to be a bearer token deserializer. I adapted the code in its "API" library and have been trying to use it to decrypt the tokens generated by my API.
I generated a <machineKey...> value using a PowerShell script from Microsoft and placed it both in the Web.config file of the API itself and the App.confg file in the test project.
The tokens still fail to decrypt, however. I receive a Exception thrown: System.Security.Cryptography.CryptographicException with the message "Error occurred during a cryptographic operation." The following is the stacktrace of the error:
at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(Func`2 func, Byte[] input)
at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(Byte[] protectedData)
at System.Web.Security.MachineKey.Unprotect(ICryptoServiceProvider cryptoServiceProvider, Byte[] protectedData, String[] purposes)
at System.Web.Security.MachineKey.Unprotect(Byte[] protectedData, String[] purposes)
at MyAPI.Tests.BearerTokenAPI.MachineKeyDataProtector.Unprotect(Byte[] protectedData) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 251
at MyAPI.Tests.BearerTokenAPI.SecureDataFormat`1.Unprotect(String protectedText) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 287
At this point I'm stumped. With the MachineKey value set to the same across the entire project, I don't see why I'm unable to decrypt the tokens. I'm guessing the cryptographic error is being deliberately vague, but I am not sure where to start with figuring this out now.
And all I wanted to do was test that the token contains the desired data in a unit test.... :-)
I was finally able to figure out a solution. I added a public variable to my Startup class that exposes the OAuthBearerAuthenticationOptions object passed into the UseBearerTokenAuthentication method. From that object, I'm able to call AccessTokenFormat.Unprotect and get a decrypted token.
I also rewrote my test to instantiate the Startup class separately, so that I have access to the value from within the test.
I still don't understand why the MachineKey thing isn't working, why I'm not able to directly unprotect the token. It would seem that as long as the MachineKey's match, I should be able to decrypt the token, even manually. But at least this seems to work, even if it's not the best solution.
This could probably be done more cleanly, for instance perhaps the Startup class could somehow detect if it's being started under test and pass the object to the test class in some other fashion rather than leaving it hanging out there in the breeze. But for now this seems to do exactly what I needed.
My startup class exposes the variable this way:
public partial class Startup
{
public OAuthBearerAuthenticationOptions oabao;
public void Configuration(IAppBuilder app)
{
// repeated code omitted
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
oabao = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(oabao);
// insert actual web API and we're off!
app.UseWebApi(config);
}
}
My test now looks like this:
[TestMethod]
public async Task Test_SignIn()
{
Startup owinStartup = new Startup();
Action<IAppBuilder> owinStartupAction = new Action<IAppBuilder>(owinStartup.Configuration);
using (var server = TestServer.Create(owinStartupAction))
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");
// repeated code omitted
// Is the access token of an appropriate length?
string access_token = responseData["access_token"].ToString();
Assert.IsTrue(access_token.Length > 32);
AuthenticationTicket token = owinStartup.oabao.AccessTokenFormat.Unprotect(access_token);
// now I can check whatever I want on the token.
}
}
Hopefully all my efforts will help someone else trying to do something similar.
Working on a project that uses Firebase for some data storage, and our client requests that the server be implemented with C#.NET. We're setting up REST endpoints on the server so that the client is able to communicate with it for a few purposes (for example, triggering an algorithm to run which can only occur on the server).
Firebase recommends we identify users via an ID token, as noted here: https://firebase.google.com/docs/auth/server/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library
Since there is no official .NET Firebase server SDK that supports token authentication, we've resorted to using a 3rd-party JWT library to do this: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
As specified in the Firebase documentation, we're first generating a sending a token to the server. After checking a few different fields in the token, we're using the kid field to grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com
We've been digging around through documentation and StackOverflow for a long time, but we can't find a way to use this public key to do this, as specified by the Firebase documentation:
Finally, ensure that the ID token was signed by the private key corresponding to the token's kid claim. Grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com and use a JWT library to verify the signature.
The Firebase documentation doesn't really offer any explanation for this, and neither does the documentation for the library we are using. So we've not been able to even get a basic idea as to how we could possibly verify the token was signed by a private key when all we are given is the public key.
What would be the best way to verify that the token was actually signed by the correct private key?
You should be able to accomplish the token validation by doing something like the following, which leverages the System.IdentityModel.Tokens.Jwt Nuget package to perform most the validations:
class Program {
static HttpClient client = new HttpClient();
static void Main() { RunAsync().Wait(); }
static async Task RunAsync() {
string encodedJwt = "[TOKEN_TO_BE_VALIDATED]";
// 1. Get Google signing keys
client.BaseAddress = new Uri("https://www.googleapis.com/robot/v1/metadata/");
HttpResponseMessage response = await client.GetAsync(
"x509/securetoken#system.gserviceaccount.com");
if (!response.IsSuccessStatusCode) { return; }
var x509Data = await response.Content.ReadAsAsync<Dictionary<string, string>>();
SecurityKey[] keys = x509Data.Values.Select(CreateSecurityKeyFromPublicKey).ToArray();
// 2. Configure validation parameters
const string FirebaseProjectId = "[FIREBASE_PROJECT_ID]";
var parameters = new TokenValidationParameters {
ValidIssuer = "https://securetoken.google.com/" + FirebaseProjectId,
ValidAudience = FirebaseProjectId,
IssuerSigningKeys = keys,
};
// 3. Use JwtSecurityTokenHandler to validate signature, issuer, audience and lifetime
var handler = new JwtSecurityTokenHandler();
SecurityToken token;
ClaimsPrincipal principal = handler.ValidateToken(encodedJwt, parameters, out token);
var jwt = (JwtSecurityToken)token;
// 4.Validate signature algorithm and other applicable valdiations
if (jwt.Header.Alg != SecurityAlgorithms.RsaSha256) {
throw new SecurityTokenInvalidSignatureException(
"The token is not signed with the expected algorithm.");
}
}
static SecurityKey CreateSecurityKeyFromPublicKey(string data) {
return new X509SecurityKey(new X509Certificate2(Encoding.UTF8.GetBytes(data)));
}
}
List of using statements for sample program:
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
Now we can use the Firebase Admin SDK for .NET.
https://github.com/Firebase/firebase-admin-dotnet
var decoded = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
var uid = decoded.Uid;
Use the following code snippet in Startup.cs to create a service that automatically validates the JWT token when a request received to the server. After using this code snippet, you have to use [Authorize] attribute above [ApiController] in controller class file(s) to force program to authenticate before giving access to the action methods in that particular controller class.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
options.Authority = "https://securetoken.google.com/<PROJECT ID>";
options.TokenValidationParameters = new TokenValidationParameters {
ValidIssuer = "https://securetoken.google.com/<PROJECT ID>",
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "<PROJECT ID>",
ValidateLifetime = true
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
}
Edit> How you should send the Token to the server.
As you see, you have to send a Post/Get request with Authorization header. The value should be in Bearer format. Please check firebase documentation to find out how to extract the token id from an authenticated user.
https://firebase.google.com/docs/reference/node/firebase.auth.Auth#signinwithemailandpassword
https://firebase.google.com/docs/reference/node/firebase.auth#usercredential
https://firebase.google.com/docs/reference/node/firebase.User#getidtoken
If you're using another Newtonsoft.JSON library like me and don't want to import the Microsoft one, try this one: https://gist.github.com/saltyJeff/41029c9facf3ba6159ac019c1a85711a
Use Verify(string token) to asynchronously verify that a token is valid: returns the unique identifier of the user if valid, and null if invalid.
Firebase has a real lack of support for c sharpers. I've created a 3rd party token verification library for the C# community. https://github.com/gtaylor44/FirebaseAuth
Most of the code is based on João Angelo's answer. I've added caching of the web request using the Cache-Control["max-age"] property in response header as documented by Firebase for better performance.