Created
January 10, 2018 04:06
-
-
Save saltyJeff/41029c9facf3ba6159ac019c1a85711a to your computer and use it in GitHub Desktop.
Authenticate Firebase Tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Net.Http; | |
using Newtonsoft.Json; | |
using System.Security.Cryptography.X509Certificates; | |
using System.Security.Cryptography; | |
class FirebaseJWTAuth { | |
public string FirebaseId; | |
private HttpClient Req; | |
public FirebaseJWTAuth(string firebaseId) { | |
FirebaseId = firebaseId; | |
Req = new HttpClient(); | |
Req.BaseAddress = new Uri("https://www.googleapis.com/robot/v1/metadata/"); | |
} | |
public async Task < string > Verify(string token) { | |
//following instructions from https://firebase.google.com/docs/auth/admin/verify-id-tokens | |
string hashChunk = token; //keep for hashing later on | |
hashChunk = hashChunk.Substring(0, hashChunk.LastIndexOf('.')); | |
token = token.Replace('-', '+').Replace('_', '/'); //sanitize for base64 on C# | |
string[] sections = token.Split('.'); //split into 3 sections according to JWT standards | |
JwtHeader header = B64Json < JwtHeader > (sections[0]); | |
if (header.alg != "RS256") { | |
return null; | |
} | |
HttpResponseMessage res = await Req.GetAsync("x509/securetoken@system.gserviceaccount.com"); | |
string keyDictStr = await res.Content.ReadAsStringAsync(); | |
Dictionary < string, string > keyDict = JsonConvert.DeserializeObject < Dictionary < string, string >> (keyDictStr); | |
string keyStr = null; | |
keyDict.TryGetValue(header.kid, out keyStr); | |
if (keyStr == null) { | |
return null; | |
} | |
using(var rsaCrypto = CertFromPem(keyStr)) { | |
using(var hasher = SHA256Managed.Create()) { | |
byte[] plainText = Encoding.UTF8.GetBytes(hashChunk); | |
byte[] hashed = hasher.ComputeHash(plainText); | |
byte[] challenge = SafeB64Decode(sections[2]); | |
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsaCrypto); | |
rsaDeformatter.SetHashAlgorithm("SHA256"); | |
if (!rsaDeformatter.VerifySignature(hashed, challenge)) { | |
return null; | |
} | |
} | |
} | |
JwtPayload payload = B64Json < JwtPayload > (sections[1]); | |
long currentTime = EpochSec(); | |
//the if chain of death | |
if (payload.aud != FirebaseId || | |
payload.iss != "https://securetoken.google.com/" + FirebaseId || | |
payload.iat >= currentTime || | |
payload.exp <= currentTime) { | |
return null; | |
} | |
return payload.sub; | |
} | |
private static RSACryptoServiceProvider CertFromPem(string pemKey) { | |
X509Certificate2 cert = new X509Certificate2(); | |
cert.Import(Encoding.UTF8.GetBytes(pemKey)); | |
return (RSACryptoServiceProvider) cert.PublicKey.Key; | |
} | |
private static byte[] SafeB64Decode(string encoded) { | |
string encodedPad = encoded + new string('=', 4 - encoded.Length % 4); | |
return Convert.FromBase64String(encodedPad); | |
} | |
private static string SafeB64DecodeStr(string encoded) { | |
return Encoding.UTF8.GetString(SafeB64Decode(encoded)); | |
} | |
private static T B64Json < T > (string encoded) { | |
string decoded = SafeB64DecodeStr(encoded); | |
return JsonConvert.DeserializeObject < T > (decoded); | |
} | |
private static DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |
public static long EpochSec() { | |
return (long)((DateTime.UtcNow - Jan1st1970).TotalSeconds); | |
} | |
private struct JwtHeader { | |
public string alg; | |
public string kid; | |
} | |
private struct JwtPayload { | |
public long exp; | |
public long iat; | |
public string aud; | |
public string iss; | |
public string sub; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment