Generating Azure AD "Look-Alike" Tokens
Published Feb 10 2020 09:00 AM 8,741 Views
Iron Contributor

JSON Web Tokens (JWTs), colloquially known as "jots", are the best thing since sliced bread in the identity developer space. Well, maybe that's stretching it a bit far, but they are fairly simple to work with when it comes to deserializing, passing around, and general dev friendliness compared to legacy formats. For cloud developers it's extra useful because it does not rely on things like being on the same corporate network as classic Active Directory Kerberos tickets prefer.

 

If you run code on Azure there's really no way avoiding them.

 

The first part of working with JWTs is acquiring the token. This usually involves an authentication "dance" where you need to interact with an identity provider either interactively or programmatically. This blog being themed around Microsoft means that provider will frequently be Azure AD, Azure AD B2C, or ADFS for that matter. It's all standards though, so if you rely on Google or Facebook instead it will be similar.

 

When developing code relying on identities it can be a hassle setting up demo accounts and all, and even if we assume there are no problems in doing so it can be annoying typing in passwords and stepping through debuggers to retrieve the token when all you want is a "simple test token".

 

Wouldn't it be easier just generating your own tokens?

 

A quick search might lead you to http://jwtbuilder.jamiekurtz.com/, and that is a good site for that purpose. The code is on GitHub as well so no complaints on my part there. Well, apart from the fact that it's done with NodeJS and things :)

 

Since I wanted to play around with Blazor (for reasons not pertaining to identity at all) I wanted to do a C#-based version.

 

While researching some B2C features I found some inspiration in the B2C samples repo as well

https://github.com/azure-ad-b2c/samples

 

The UI should be fairly self-explanatory:

jwt_generator.png

 

Behind the scenes a certificate is used for signing the token, so in case you want to mock the validation in an API (which is part of the purpose for this tool) the necessary OpenID Connect metadata endpoints are exposed as well:

https://fqdn/.well-known/openid-configuration and a corresponding JWKS endpoint at

https://fqdn/.well-known/keys.

 

Code can be found at:

https://github.com/ahelland/Identity-CodeSamples-v2/tree/master/blazor-jwt_generator-dotnet-core

 

Most of the code is "fluff" in the sense that it's mostly about setting up the UI, and related tasks. The piece you should be most interested in is the following:

 

 

 

public static string BuildIdToken(string Subject)
{
    string issuer = jwt.Issuer;
    string audience = jwt.Audience;

    IList<System.Security.Claims.Claim> claims = new List<System.Security.Claims.Claim>();
    claims.Add(new System.Security.Claims.Claim("ver", jwt.Version, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("sub", Subject, System.Security.Claims.ClaimValueTypes.String, issuer));            
    claims.Add(new System.Security.Claims.Claim("iat", jwt.iat, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("name", jwt.name, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("preferred_username", jwt.preferred_username, System.Security.Claims.ClaimValueTypes.String, issuer));            
    claims.Add(new System.Security.Claims.Claim("oid", jwt.oid, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("tid", jwt.tid, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("nonce", jwt.nonce, System.Security.Claims.ClaimValueTypes.String, issuer));
    claims.Add(new System.Security.Claims.Claim("aio", jwt.aio, System.Security.Claims.ClaimValueTypes.String, issuer));

    // Create the token
    JwtSecurityToken token = new JwtSecurityToken(
            issuer,
            audience,
            claims,
            DateTime.Parse(jwt.IssuedAt),                    
            DateTime.Parse(jwt.Expiration),
            SigningCredentials.Value);

    
    //AAD v2 tokens doesn't use the x5t claim
    token.Header.Remove("x5t");

    //AAD doesn't use kid/thumbprint, but the hash so a "hack" is required
    token.Header.Remove("kid");
    token.Header.Add("kid", SigningCertHash);

    // Get the representation of the signed token
    JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();

    return jwtHandler.WriteToken(token);
}

 

 

 

 

And there's a Docker-image (Linux) at:

https://hub.docker.com/r/ahelland/blazor-jwt_generator-dotnet-core-linux

 

I've tested running it as a container on Azure App Service - more info on how to do that here:

https://contos.io/playing-with-container-in-azure-app-service-bd397be2bb4c

 

The app runs on both Windows and Linux (and Docker-based variants of these), but since certificate handling is different between the two platforms the code branches based on setting the HostEnvironment variable to "Windows" or "Linux".

 

Windows 

For Windows it is assumed that the certificate is stored in the current user's certificate store.

 

To generate (and store) a certificate use the following PowerShell commands:

 

 

 

$cert = New-SelfSignedCertificate -Type Custom -Subject "CN=MySelfSignedCertificate" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3") -KeyUsage DigitalSignature -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(2) -CertStoreLocation "Cert:\CurrentUser\My" 

$cert.Thumbprint

 

 

 

 

Linux 

For Linux it is assumed that the certificate is stored in /var/ssl/private/{SigningCertThumbprint}.p12 

Technically it can be stored in any path you like, but this ensures compatibility with deploying to Azure App Service and having the certificate stored in Azure Key Vault.

 

To generate a compatible certificate and retrieve the thumbprint run the following (tested on Ubuntu 18.04 on WSL):

 

 

 

openssl req -x509 -newkey rsa:4096 -keyout myKey.pem -out cert.pem -days 365 -nodes

openssl pkcs12 -export -out keyStore.p12 -inkey myKey.pem -in cert.pem

openssl x509 -in cert.pem -noout -fingerprint

 

 

 

 

For both operating systems set the thumbprint in the SigningCertThumbprint setting in appsettings.json.

 

Sure, not the most impressive code you've ever seen, but it serves its purpose :)

 

The app has templates for Azure AD and Azure AD B2C tokens in addition to a generic token not specific to any identity provider. If you test the tokens at https://jwt.ms they will be interpreted as intended - the AAD-templates will generate tokens identified as being sourced from Azure AD.

 

This clearly demonstrates why you should validate tokens issued by Azure properly, but token validation would be a topic for a different post at another time :)

Version history
Last update:
‎Feb 09 2020 11:14 AM
Updated by: