in FirebaseAdmin/FirebaseAdmin/Auth/Jwt/FirebaseTokenVerifier.cs [162:264]
internal async Task<FirebaseToken> VerifyTokenAsync(
string token, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(token))
{
throw new ArgumentException($"{this.shortName} must not be null or empty.");
}
string[] segments = token.Split('.');
if (segments.Length != 3)
{
throw this.CreateException($"Incorrect number of segments in {this.shortName}.");
}
var header = JwtUtils.Decode<JsonWebSignature.Header>(segments[0]);
var payload = JwtUtils.Decode<FirebaseToken.Args>(segments[1]);
var projectIdMessage = $"Make sure the {this.shortName} comes from the same Firebase "
+ "project as the credential used to initialize this SDK.";
var verifyTokenMessage = $"See {this.url} for details on how to retrieve a value "
+ $"{this.shortName}.";
var issuer = this.issuer + this.ProjectId;
string error = null;
var errorCode = this.invalidTokenCode;
var currentTimeInSeconds = this.clock.UnixTimestamp();
if (!this.IsEmulatorMode && string.IsNullOrEmpty(header.KeyId))
{
if (payload.Audience == FirebaseAudience)
{
error = $"{this.operation} expects {this.articledShortName}, but was given a custom "
+ "token.";
}
else if (header.Algorithm == "HS256")
{
error = $"{this.operation} expects {this.articledShortName}, but was given a legacy "
+ "custom token.";
}
else
{
error = $"Firebase {this.shortName} has no 'kid' claim.";
}
}
else if (!this.IsEmulatorMode && header.Algorithm != "RS256")
{
error = $"Firebase {this.shortName} has incorrect algorithm. Expected RS256 but got "
+ $"{header.Algorithm}. {verifyTokenMessage}";
}
else if (this.ProjectId != payload.Audience)
{
error = $"Firebase {this.shortName} has incorrect audience (aud) claim. Expected "
+ $"{this.ProjectId} but got {payload.Audience}. {projectIdMessage} "
+ $"{verifyTokenMessage}";
}
else if (payload.Issuer != issuer)
{
error = $"Firebase {this.shortName} has incorrect issuer (iss) claim. Expected "
+ $"{issuer} but got {payload.Issuer}. {projectIdMessage} {verifyTokenMessage}";
}
else if (payload.IssuedAtTimeSeconds - ClockSkewSeconds > currentTimeInSeconds)
{
error = $"Firebase {this.shortName} issued at future timestamp "
+ $"{payload.IssuedAtTimeSeconds}. Expected to be less than "
+ $"{currentTimeInSeconds}.";
}
else if (payload.ExpirationTimeSeconds + ClockSkewSeconds < currentTimeInSeconds)
{
error = $"Firebase {this.shortName} expired at {payload.ExpirationTimeSeconds}. "
+ $"Expected to be greater than {currentTimeInSeconds}.";
errorCode = this.expiredIdTokenCode;
}
else if (string.IsNullOrEmpty(payload.Subject))
{
error = $"Firebase {this.shortName} has no or empty subject (sub) claim.";
}
else if (payload.Subject.Length > 128)
{
error = $"Firebase {this.shortName} has a subject claim longer than 128 characters.";
}
else if (this.TenantId != null && this.TenantId != payload.Firebase?.Tenant)
{
error = $"Firebase {this.shortName} has incorrect tenant ID. Expected "
+ $"{this.TenantId} but got {payload.Firebase?.Tenant}";
errorCode = AuthErrorCode.TenantIdMismatch;
}
if (error != null)
{
throw this.CreateException(error, errorCode);
}
await this.VerifySignatureAsync(segments, header.KeyId, cancellationToken)
.ConfigureAwait(false);
var allClaims = JwtUtils.Decode<Dictionary<string, object>>(segments[1]);
// Remove standard claims, so that only custom claims would remain.
foreach (var claim in StandardClaims)
{
allClaims.Remove(claim);
}
payload.Claims = allClaims.ToImmutableDictionary();
return new FirebaseToken(payload);
}