| | | 1 | | using Microsoft.IdentityModel.JsonWebTokens; |
| | | 2 | | |
| | | 3 | | namespace ArturRios.Heimdall.WebApi.Security; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Whether a token names Heimdall as its issuer and one of the audiences Heimdall issues it |
| | | 7 | | /// under — the check every reader of a Heimdall token makes beside the signature and lifetime. |
| | | 8 | | /// </summary> |
| | | 9 | | /// <remarks> |
| | | 10 | | /// <para> |
| | | 11 | | /// <b>Why it exists.</b> The signing key is shared with other services today (Threat Model |
| | | 12 | | /// TB-5), and <c>ArturRios.Jwt</c> checks only the signature and lifetime. Without this, a |
| | | 13 | | /// token signed with that key for another service's audience, or under another issuer, would |
| | | 14 | | /// be read here as one of Heimdall's own. Each reader names the audiences it takes: the |
| | | 15 | | /// bearer-token validator the three person audiences (<see cref="PersonTokenAudiences" />), |
| | | 16 | | /// the challenge reader the challenge audience alone, and the application-token reader the |
| | | 17 | | /// application audience alone. |
| | | 18 | | /// </para> |
| | | 19 | | /// <para> |
| | | 20 | | /// <b>Exact.</b> The issuer must equal the configured one, ordinally; a token without an |
| | | 21 | | /// <c>iss</c> passes only when none is configured, which is how Heimdall then issues. A token |
| | | 22 | | /// must carry exactly one audience, because Heimdall never issues one with several; no |
| | | 23 | | /// <c>aud</c> at all counts as the empty audience, again how Heimdall issues when none is |
| | | 24 | | /// configured. Run it only on a token whose signature has been checked: before that, the |
| | | 25 | | /// claims are whatever the sender wrote. |
| | | 26 | | /// </para> |
| | | 27 | | /// </remarks> |
| | | 28 | | public static class TokenOrigin |
| | | 29 | | { |
| | | 30 | | /// <summary> |
| | | 31 | | /// Whether <paramref name="token" /> was issued by <paramref name="issuer" /> for exactly one |
| | | 32 | | /// of <paramref name="audiences" />. An unreadable token is not. |
| | | 33 | | /// </summary> |
| | | 34 | | public static bool IsIssuedFor(string token, string issuer, params IReadOnlyCollection<string> audiences) |
| | | 35 | | { |
| | | 36 | | try |
| | | 37 | | { |
| | 1067 | 38 | | var jwt = new JsonWebToken(token); |
| | 1064 | 39 | | var tokenAudiences = jwt.Audiences.ToList(); |
| | | 40 | | |
| | 1064 | 41 | | var audience = tokenAudiences.Count switch |
| | 1064 | 42 | | { |
| | 1 | 43 | | 0 => string.Empty, |
| | 1060 | 44 | | 1 => tokenAudiences[0], |
| | 3 | 45 | | _ => null |
| | 1064 | 46 | | }; |
| | | 47 | | |
| | 1064 | 48 | | return audience is not null && |
| | 1064 | 49 | | string.Equals(jwt.Issuer ?? string.Empty, issuer, StringComparison.Ordinal) && |
| | 1064 | 50 | | audiences.Contains(audience, StringComparer.Ordinal); |
| | | 51 | | } |
| | 3 | 52 | | catch (Exception) |
| | | 53 | | { |
| | | 54 | | // Total, like the claims reader: a token whose header, issuer or audience cannot be read |
| | | 55 | | // is refused as unauthenticated, never failed with a 500. |
| | 3 | 56 | | return false; |
| | | 57 | | } |
| | 1067 | 58 | | } |
| | | 59 | | } |