Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Validates the JWT Tokens send by apple during authentication
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
/// <summary>
/// Validates the JWT Tokens send by apple during authentication
/// </summary>
public class AppleTokenValidator : IDisposable
{
private const string ApplePublicKeysEndpoint = "https://appleid.apple.com/auth/keys";
private readonly HttpClient _httpClient;
/// <summary>
/// Initializes a new instance of the <see cref="AppleTokenValidator"/> class.
/// </summary>
public AppleTokenValidator() : this(new HttpClient()) { }
/// <summary>
/// Initializes a new instance of the <see cref="AppleTokenValidator"/> class.
/// </summary>
public AppleTokenValidator(HttpClient httpClient)
{
_httpClient = httpClient;
}
/// <summary>
/// Validates a Apple-issued Json Web Token (JWT).
/// </summary>
/// <exception cref="HttpRequestException">If Apple doesn't return an associated public key.</exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="SecurityTokenDecryptionFailedException"></exception>
/// <exception cref="SecurityTokenEncryptionKeyNotFoundException"></exception>
/// <exception cref="SecurityTokenException"></exception>
/// <exception cref="SecurityTokenExpiredException"></exception>
/// <exception cref="SecurityTokenInvalidAudienceException"></exception>
/// <exception cref="SecurityTokenInvalidLifetimeException"></exception>
/// <exception cref="SecurityTokenInvalidSignatureException"></exception>
/// <exception cref="SecurityTokenNoExpirationException"></exception>
/// <exception cref="SecurityTokenNotYetValidException"></exception>
/// <exception cref="SecurityTokenReplayAddFailedException"></exception>
/// <exception cref="SecurityTokenReplayDetectedException"></exception>
public async Task<SecurityToken> ValidateAsync(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentException("token is required");
}
var response = await _httpClient.GetAsync(ApplePublicKeysEndpoint);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
}
var applePublicKeys = await JsonSerializer.DeserializeAsync<AppleKeys>(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var kid = new JwtSecurityTokenHandler().ReadJwtToken(token).Header.Kid;
var publicKey = applePublicKeys.Keys.FirstOrDefault(key => key.Kid == kid);
if (publicKey == null)
{
throw new Exception($"kid {kid} not found in apple public keys");
}
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
IssuerSigningKey = publicKey,
ValidateAudience = false,
ValidateIssuer = false,
};
tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
return validatedToken;
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
public class AppleKeys
{
public IReadOnlyCollection<JsonWebKey> Keys { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.