Add support for validating/decoding embedded app session tokens
Now that browsers are starting to block third-party cookies by default, Shopify has added a session token to embedded apps which can be used to validate that a user is authenticated without relying on cookies. Whenever an app is loaded in an embedded context, the querystring will contain the usual hmac stuff along with a session value. The session string is a typical JWT and can be validated/decoded as such.
I plan to add support for this to ShopifySharp's AuthorizationService.
I'm planning on taking on a dependency to Microsoft.IdentityModel.JsonWebTokens instead of implementing the JWT parsing manually.
FYI, I use session tokens in my app already and didn't need to decode tokens manually as it is handled by ASP.NET 6 already.
@clement911 I'm with you, after implementing it in a test app I don't think the validation/decoding feature is necessary at all! At best we might be able to provide some kind of default class that the session could be decoded into (e.g. token.dest becomes token.ShopUrl).
@clement911 Could you tell me how you use session token in .NET core 6? Thanks Deepak
@deepakkumar1984 I use the AddJwtBearer method to configure it with my API keys.
Like this:
services.AddJwtBearer("ShopifySession", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = "ShopifySession",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(shopifyConfig.SecretKey)),
ValidAudience = shopifyConfig.ApiKey,
ValidateLifetime = true,//ensure not expired
ValidateAudience = true,//ensure for correct api key
ValidateIssuerSigningKey = true,//ensure for correct secret key
ValidateIssuer = false,//issuer is the shop's domain, which we don't yet know
ValidateActor = false,//not used by Shopify Session Tokens
};
})
On the client side (browser), you need to get the session token with AppBridge and set the Authorization HTTP Header to 'Bearer
Then the HttpContext.User.Identities should contain an additional ClaimsIdentity.
I used the following method to extract the shop id and user id from the ClaimsIdentity.
public bool TryGetShopifySessionInfo(ClaimsIdentity claims, out string shopId, out long userId)
{
shopId = null;
userId = 0;
string iis = claims.FindFirst("iss")?.Value;
if (iis == null)
{
_logger.LogWarning("Shopify JWT had no iis value");
return false;
}
if (!Uri.IsWellFormedUriString(iis, UriKind.Absolute))
{
_logger.LogWarning($"Shopify JWT iis value {iis} is not a valid url");
return false;
}
var iisUri = new Uri(iis);
string dest = claims.FindFirst("dest")?.Value;
if (dest == null)
{
_logger.LogWarning("Shopify JWT had no dest value");
return false;
}
if (!Uri.IsWellFormedUriString(dest, UriKind.Absolute))
{
_logger.LogWarning($"Shopify JWT iis value {dest} is not a valid url");
return false;
}
var destUri = new Uri(dest);
if (iisUri.GetLeftPart(UriPartial.Authority) != destUri.GetLeftPart(UriPartial.Authority))
{
_logger.LogWarning($"Shopify JWT iis {iis} and dest {dest} didn't match on their left part.");
return false;
}
shopId = destUri.Host;
string sub = claims.FindFirst(ClaimTypes.NameIdentifier)?.Value //seems asp.net automatically maps 'sub' to another name
?? claims.FindFirst("sub")?.Value; //just in case it changes in a future aspnet version
if (!long.TryParse(sub, out userId))
{
_logger.LogWarning($"Shopify JWT sub {sub} doesn't parse to a numeric user id");
return false;
}
return true;
}
@clement911 Thank you so much for helping me out mate!