I have had great success with the Microsoft Graph API to access users etc etc within Azure Active Directory, however two things that still require EWS and SOAP are retrieving user photos and adding a mail rule to a users mail account.
I'm using Service accounts for everything, and impersonating an account admin to make requests.
After attempting to use the same access token that I am using against the Graph API, I receive the error:
The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.
Reading around, I understand that because EWS requires full privileges against the accounts, you can't just pass the access token, but you also have to "do something" with an x509 certificate.
In my registered app, within Azure, I have adjusted the manifest so to include a self signed certificate so that I have:
"keyCredentials": [{
"customKeyIdentifier": "lhbl...../w0bjA6l1mQ8=",
"keyId": "774D2C35-2D58-.....-AC34B15472BA",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": "MIIFtTCCA52gAwIB.....mmgufQ2rW5GSjEEXOlO1c7qw=="
}],
My understanding is the customKeyIdentifier is the Base64 of the key, from the command: echo $(openssl x509 -in cert.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
the value is literally the key content, with the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- removed, and all new lines removed too (otherwise in the manifest, the json isn't valid).
The keyId is a GUID I just generated on the terminal with the uuidgen command, I don't think its related directly to the certificate in any way.
What I'm not sure then, is what I have to change within my code, that is going to try to auth against EWS.
I have started out with the node-ews library, my configuration looks like:
var ewsConfig = {
username: userEmail,
token: self.accessToken,
host: 'https://outlook.office365.com/EWS/Exchange.asmx',
auth: 'bearer'
};
var ews = new EWS(ewsConfig);
var ewsFunction = 'UpdateInboxRules';
ews.run(ewsFunction, ewsArgs)
.then(result => {
cb(null, result)
})
.catch(err => {
cb(err);
});
};
self.accessToken is the same token that I receive when accessing the Microsoft Graph API.
So, in conclusion, my questions are:
What do I need to do to my request so that I am telling the server to also auth the x509 certificate, I read that I may need to convert it to a PKCS12 certicificate also?
Can I use the same accessToken that I am successfully using to access the graph api?
Is there a code snippet anywhere for Nodejs doing this?
Is the keyId ok to be any identifier I want to give it?
The response I get back contains:
{ 'content-length': '0',
server: 'Microsoft-IIS/8.5',
'request-id': '9b0d7a1b-85e6-40f6-9af0-7f65fc6669dc',
'x-calculatedfetarget': 'MM1P123CU001.internal.outlook.com',
'x-backendhttpstatus': '401, 401',
'set-cookie': [Object],
'x-feproxyinfo': 'MM1P123CA0026.GBRP123.PROD.OUTLOOK.COM',
'x-calculatedbetarget': 'MM1P123MB1337.GBRP123.PROD.OUTLOOK.COM',
'x-ms-diagnostics': '2000001;reason="The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.";error_category="invalid_token"',
'x-diaginfo': 'MM1P123MB1337',
'x-beserver': 'MM1P123MB1337',
'x-feserver': 'MM1P123CA0026, VI1PR0701CA0059',
'x-powered-by': 'ASP.NET',
'www-authenticate': 'Bearer client_id="00000002-0000-0ff1-ce00-000000000000", trusted_issuers="00000001-0000-0000-c000-000000000000#*", token_types="app_asserted_user_v1 service_asserted_app_v1", authorization_uri="https://login.windows.net/common/oauth2/authorize", error="invalid_token",Basic Realm="",Basic Realm="",Basic Realm=""',
date: 'Tue, 02 May 2017 18:08:54 GMT',
connection: 'close' } }
Thanks, much appreciated
I followed this article to generate access_token https://blogs.msdn.microsoft.com/arsen/2015/09/18/certificate-based-auth-with-azure-service-principals-from-linux-command-line/, did have some issues with jwt signing, I had to use openssl rsa -check -in key.pem to decrypt the key and save it in a text file. then jwt signing worked. You also need to be impersonating, see this https://github.com/CumberlandGroup/node-ews/issues/39
it may help with node-ews. I have not tested this scenario with node-ews. If you are interested in looking at more robust approach with ews managed api like coding, I have ported c# version of ews-managed-api to ews-javascript-api , here is the sample code to achieve same with ews-javascript-api, tested and confirmed working code.
var ews = require("ews-javascript-api");
ews.EwsLogging.DebugLogEnabled = false;
var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2013);
exch.Credentials = new ews.OAuthCredentials("oauth access_token");
exch.Url = new ews.Uri("https://outlook.office365.com/Ews/Exchange.asmx");
exch.ImpersonatedUserId = new
ews.ImpersonatedUserId(ews.ConnectingIdType.SmtpAddress, "user#domain.com");
exch.HttpHeaders = { "X-AnchorMailbox": "user#domain.com" };
var rule = new ews.Rule();
rule.DisplayName = "MoveInterestingToJunk";
rule.Priority = 1;
rule.IsEnabled = true;
rule.Conditions.ContainsSubjectStrings.Add("Interesting");
rule.Actions.MoveToFolder = new ews.FolderId(ews.WellKnownFolderName.JunkEmail);
var ruleop = new ews.CreateRuleOperation(rule);
exch.UpdateInboxRules([ruleop], true)
.then(function (response) {
console.log("success - update-inboxrules");
ews.EwsLogging.Log(response, true, true);
}, function (err) {
debugger;
console.log("error in update-inboxrules");
ews.EwsLogging.Log(err, true, true);
});
Related
I need to create service that calls graph api to access company data. In order to authenticate I need JWT token from Azure Active Directory. The authentication will be using application mode with signing certificate. I tried to use MSAL node ConfidentialClientApplication but the service needs to use http proxy to connect to internet. To my knowledge MSAL node does not support this and calls result in library being unable to resolve the address of "https://login.microsoftonline.com". How can I make MSAL node use the proxy or get JWT token without use od MSAL?
In order to get JWT token from azure active directory without MSAL node, one have to generate proper JWT token on its own and then sign it with certificate private key. The header of the token consists of following fields:
{
typ: "JWT",
alg: "RS256",
kid: "156E...",
x5t: "iTYVn..."
}
"kid" is the thumbprint of the certificate used to sign the request - here is a good example how to obtain it for pfx file with powershell https://stackoverflow.com/a/32980899/3588432
"x5t" is base64 encoded and sanitized certificate thumbprint.
Sanitization of base64 encoded string means:
trimming "=" signs at the end
replace "/" with "_"
replace "+" with "-"
Exemplary C# code for the sanitization:
var sanitized = s.Split('=')[0].Replace('+', '-').Replace('/', '_');
and JS code:
var sanitized = s.split('=')[0].replace('+', '-').replace('/', '_');
The payload of the token consists of the following fields:
{
aud: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
iss: "{clientId}",
nbf: 1617952610,
exp: 1617953210,
sub: "{clientId}",
jti: "e13efcf..."
}
{tenantId} and {clientId} are Azure AD data of application we are authenticating to
"nbf" is the time when the token will began to be valid, normally it is time the token got generated. It has unix epoch format https://en.wikipedia.org/wiki/Unix_time and is an integer.
"exp" - the time the token expires in unix epoch format.
"jti" - a unique token identifier. It may be random generated guid. Should be different for every request.
An example how to get "nbf" value in JavaScript:
var nbf = Math.floor(new Date().getTime() / 1000);
When ready header and payload should be serialized (with sanitization) on concatenated with ".":
var token = JSON.stringify(header) + "." + JSON.stringify(payload);
Then we need to sign it with certificate private key, encode it with base 64 (with sanitization) and prepare a clientAssertion value:
var clientAssertion = token + "." + signedToken;
As a last step can send request to get JWT token:
const body = new URLSearchParams();
const token = await fetch("https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", {
agent: new HttpsProxyAgent("http://..."),
body: new URLSearchParams({
"client_assertion": clientAssertion,
"client_id": "{clientId}",
"scope": "https://graph.microsoft.com/.default"
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
"grant_type": "client_credentials"
}),
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded"
}
})
.then(response => response.json().access_token);
Edit: I found the answer. Scroll to the bottom of this question.
I am working on a NodeJS authentication server and I would like to sign JSON Web Tokens (JWT) using google signatures.
I am using Google Cloud Key Management Service (KMS) and I created a key ring and an asymmetric signing key.
This is my code to get the signature:
signatureObject = await client.asymmetricSign({ name, digest })
signature = signatureObject["0"].signature
My Google signature object looks like this:
My question: How do I sign a JWT using the Google signature?
Or in other words, how do I concatenate the Google signature to the (header.payload) of the JWT?
The JWT should look something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (GoogleSignature)
The Code I am using:
signing:
async function sign(message, name) {
hashedMessage = crypto.createHash('sha256').update(message).digest('base64');
digest = { 'sha256': hashedMessage }
signatureObject = await client.asymmetricSign({ name, digest }).catch((err) => console.log(err))
signature = signatureObject["0"].signature
signJWT(signature)
}
Creating the JWT:
function signJWT(signature) {
header = {
alg: "RS256",
typ: "JWT"
}
payload = {
sub: "1234567890",
name: "John Doe",
iat: 1516239022
}
JWT = base64url(JSON.stringify(header)) + "." +
base64url(JSON.stringify(payload)) + "." +
???signature??? ; // what goes here?
}
Verifying:
async function validateSignature(message, signature) {
// Get public key
publicKeyObject = await client.getPublicKey({ name }).catch((err) => console.log(err))
publicKey = publicKeyObject["0"].pem
//Verify signature
var verifier = crypto.createVerify('sha256');
verifier.update(message)
var ver = verifier.verify(publicKey, signature, 'base64')
// Returns either true for a valid signature, or false for not valid.
return ver
}
The Answer:
I can use the toString() method like so:
signatureString = signature.toString('base64');
AND then I can get the original signature octet stream by using
var buffer = Buffer.from(theString, 'base64');
You did not post your code in your question, so I do not know how you are building the JWT for signing.
[EDIT 1/18/2019 after code added to question]
Your code is doing the signature backwards. You are creating a signature and trying to attach it to the JWT Headers + Payload. You want to instead take the JWT Headers + Payload and sign that data and then attach the signature to the JWT to create a Signed-JWT.
Psuedo code using your source code:
body_b64 = base64url(JSON.stringify(header)) + "." + base64url(JSON.stringify(payload))
signature = sign(body_b64, name);
jwt = body_b64 + '.' + base64url(signature)
Note: I am not sure what data format the signature is returned by signatureObject["0"].signature. You may have to convert this before converting to base64.
[END EDIT]
Example data:
JWT Header:
{
alg: RS256
kid: 0123456789abcdef62afcbbf01234567890abcdef
typ: JWT
}
JWT Payload:
{
"azp": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
"aud": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
"sub": "123456789012345678901",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"exp": "1547806224",
"expires_in": "3596",
"email": "someone#example.com.com",
"email_verified": "true",
"access_type": "offline"
}
Algorithm:
SHA256withRSA
To create a Signed JWT (JWS):
Step 1:
Take the JWT Header and convert to Base-64. Let's call this hdr_b64.
Step 2:
Take the JWT Payload and convert to Base-64. Let's call this payload_b64.
Step 3:
Concatenate the encoded header and payload with a dot . in between: hdr_b64 + '.' + payload_b64`. Let's call this body_b64.
Step 4:
Normally a JWS is signed with SHA256withRSA often called "RS256" using the Private Key:
signature = sign(body_b64, RS256, private_key)
Now convert the signature to Base-64. Let call this signature_b64.
To create the final JWS:
jws = body_b64 + '.' + signature_b64.
Recommendations:
Do you want to use KMS to create Signed JWTs? I would not recommend this. There is a cost accessing keys stored in KMS. Signed-JWTs are signed with the private key and verified with the public key. How are you going to publish the public key? What performance level do you need in accessing the private and public keys (how often will you be signing and verifying)?
When you create a service account in Google Cloud Platform, a keypair is created for you. This keypair has an ID with the public key available on the Internet and the private key is present in the Service Account Json credentials file. I would use a Service Account to create Signed-JWTs instead of a keypair in KMS.
Example code in Python to create and sign:
def create_signed_jwt(pkey, pkey_id, email, scope):
'''
Create a Signed JWT from a service account Json credentials file
This Signed JWT will later be exchanged for an Access Token
'''
import jwt
# Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
auth_url = "https://www.googleapis.com/oauth2/v4/token"
issued = int(time.time())
expires = issued + expires_in # expires_in is in seconds
# Note: this token expires and cannot be refreshed. The token must be recreated
# JWT Headers
headers = {
"kid": pkey_id, # This is the service account private key ID
"alg": "RS256",
"typ": "JWT" # Google uses SHA256withRSA
}
# JWT Payload
payload = {
"iss": email, # Issuer claim
"sub": email, # Issuer claim
"aud": auth_url, # Audience claim
"iat": issued, # Issued At claim
"exp": expires, # Expire time
"scope": scope # Permissions
}
# Encode the headers and payload and sign creating a Signed JWT (JWS)
sig = jwt.encode(payload, pkey, algorithm="RS256", headers=headers)
return sig
I am having an issue with validating the JWT on the server side end of my node/express app. The token is being generated in Identity Server in an asp.net core app. The token that is generated is an RS256 token type, which means a private key and public key need to be generated on creation in the Identity Server. I need to retrieve a valid certificate with a valid signature.
On the client side (Angular) I'm passing in the Bearer token on all requests once signed in. I need to authenticate that token somehow. The way to do that with a RS256 token type is to make sure the public key matches. I'm using
const jwt2 = require('jwt-simple');
For my JWT validation.
The issue is the secret, here is the jwt-simple documentation jwt-simple link. If I make the third value in decode false it works, because it's ignoring the secret/cert that is required.
I'm making this validation in the middleware so all endpoints will hit it.
I saw this issue - SO Similar Issue and ran those same commands. I'm still getting the error because the token doesn't really have anything to do with the certs because I'm getting it from the Identity Server project. So I need to retrieve the cert public key from that project.
How would I be able to send that cert in the token or retrieve that valid cert somehow?
v1 - (using the self signed server.crt as the cert and getting this error)
Error: Signature verification failed
App.js
//This is for a self-signed certificate locally with no correlation to the token itself.
const options = {
key: fs.readFileSync('./key.pem', 'utf8'),
cert: fs.readFileSync('./server.crt', 'utf8')
};
app.use((req, res, next) => {
if(!req.headers.authorization){
return res.status(403).json({ error: 'No credentials sent!'});
} else {
let token = req.headers.authorization.split(' ')[1]
var decoded = jwt.decode(token, options.cert);
if(decoded){
let currentTime = new Date().getTime()/1000
if(decoded.exp <= currentTime){
return res.status(403).json({
error: 'Token has expired'
});
}
}
else if(!decoded){
return res.status(403).json({
error: 'invalid token'
});
}
}
next();
})
JWT.io parsed token structure -
Header
{
"alg": "RS256",
"kid": "1231231231231231231",
"typ": "JWT",
"x5t": "si7bdXd6......HnxhO4Wi_s"
}
Do I do anything with x5t? Apologies for the long post. Thanks.
If the signing public key is provided with the token and you blindly trust it, it basically defeats the purpose of having signed tokens.
You need some other mechanism for sharing the authentication service's public key. Depending on your requirements for key rotations and how your app works in general, you can either get it from a static path/url when your app starts up, or you may want to build in a mechanism to periodically check for updated valid public key(s).
Scenario:
I have an angular5 client application, which uses hello.js to authenticate users using their office 365 credentials.
Client Code:
hello.init({
msft: {
id: configuration.AppID,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/' + configuration.TenantID + '/oauth2/v2.0/authorize'
},
scope_delim: ' ',
form: false
},
},
{ redirect_uri: configuration.redirecturl }
);
}
login() {
hello('msft').login({ scope: 'User.Read People.Read', display: 'popup' })
.then((authData: any) => { // console.log(authData);
this.zone.run(() => {
// get profile
}
A successful response is (Manipulated for security reasons)
{
"msft":{
"access_token":"REMOVED TOKEN HERE",
"token_type":"Bearer",
"expires_in":3599,
"scope":"basic,User.Read,People.Read",
"state":"",
"session_state":"3b82898a-2b3f-445363f-89ae-d9696gg64ad3",
"client_id":"672330148-2bb43-3080-9eee-1f46311f789c",
"network":"msft",
"display":"popup",
"redirect_uri":"http://localhost:5653/",
"expires":15245366.218
}
}
The decoded access_token has these few keys:
Header:
1. nonce (requires some special processing, I couldn't find any documentation regarding special processing)
2. typ: JWT
PayLoad:
"aud": "https://graph.microsoft.com",
Once the access_token is received, I am sending the access_token in authorization header of every call to my backend API. The goal is to validate the token and only send a successful response if the access_token is validated and authorized. If unsuccessful, 401 Unauthorized is the response.
API Code to validate access_token, ASP .NET CORE 2, Following (https://auth0.com/blog/securing-asp-dot-net-core-2-applications-with-jwts/)
namespace JWT
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddMvc();
}
}
}
// other methods
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
In appsettings.json I have:
{ "Jwt": {
"Key": "verySecretKey", **(I got the key from https://login.microsoftonline.com/common/discovery/keys with the kid value in access_token header)**
"Issuer": "https://sts.windows.net/49bcf059-afa8-4bf9-8470-fad0c9cce27d/", } }
Finally, the error I receive is :
"WWW-Authenticate →Bearer error="invalid_token", error_description="The signature key was not found""
I have been stuck here since past few days, any help will be life savior.
Key Points:
I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.
The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?
Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.
Please let me know if you need more information.
I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.
Microsoft Graph API access tokens are signed differently from other access tokens from what I can see.
You do not need to validate tokens that are meant for another API, it is their job.
The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?
I don't know about HelloJS, but you should be able to get an Id token after authentication with response_type=id_token token.
Then you need to attach that to the requests.
It should have your client id as the audience.
Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.
The only thing that stands out to me is that you are doing a lot of unnecessary configuration.
Basically the configuration should be:
.AddJwtBearer(o =>
{
o.Audience = "your-client-id";
o.Authority = "https://login.microsoftonline.com/your-tenant-id/v2.0";
})
The handler will automatically fetch the public signing keys on startup.
It's not really a good idea to hard-code signing keys in your app since your app will break when AAD finishes signing key rollover.
I also spent a lot of time trying to validate it, but the bottom line is that you can't:
Access tokens are opaque blobs of text that are for the resource only. If you're a client getting a token for Graph, assume that it's an encrypted string that you should never look at - sometimes it will be. We use a special token format for Graph that they know how to validate - you shouldn't be looking at access tokens if they're not for you. (source: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609)
Instead of using the access token, you should create an ID token, which is a regular JWT token that can be validated like any other JWT:
Get the public key from the Microsoft directory
Validate the signature, audience, issuer, etc.
To get an ID token using the MSAL API after login you can do (javascript example):
const { instance, accounts } = useMsal();
const request = {
scopes: ["User.Read"],
account: accounts[0]
};
const idToken = await instance.acquireTokenSilent(request).idToken;
For more information on ID Tokens, please check:
https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens
For more information on opaque tokens, please check:
https://zitadel.com/blog/jwt-vs-opaque-tokens
Yeah, this took a bit to work through. For anyone else researching this, here's my understanding.
You don't use the Microsoft Graph API to secure your web api. Instead:
The client continues to use the Microsoft Identity Platform to authenticate.
The client uses the resulting JWT access token to call the Web API as normal for OAuth 2.0 flow
The web API uses JwtBearerAuthenticationScheme, setting the authority to the Microsoft identity platform. See this example and search for JwtBearerAuthenticationScheme.
The web API uses the provided access token to obtain an 'On Behalf Of' user token.
The web API calls the Graph API using this 'On Behalf Of' token. This token has a different lifespan than the token the client obtained, and refreshes must be handled separately.
This is a very distilled version of this example. Disclaimer: I haven't put this into practice yet.
I am trying to POST a JSON Web Token to the Lets Encrypt new registration endpoint using Node.js. How do I create this token? This is some code I've been experimenting with to try to generate a token that Let's Encrypt's webserver will accept:
var jwt = require('jsonwebtoken');
var jws = require('jws');
var crypto = require('crypto');
var pem = require('pem');
var jose = require('node-jose');
var keystore = jose.JWK.createKeyStore();
var key;
var props = {
//kid: 'gBdaS-adsfasdfasdfsa',
alg: 'HS256',
//use: 'enc',
n: "pK7LuT2hxkWnYRl1Tcw9iAy9-_TqvHp2wh6EcHq_wglsNmtpxAe9gNGZevWu6T2O1aEmPYkgy7Q1meKNifenFuWicDcSSenkMM0JApfdveiVqjBA81EL0Y76T8i2JolggGXbiSa_ZRGwG-0FPDSIX3Jy5mQgOn-t-zrhD9yLDn2N7zzFqCBOtxzrwz1HEtN8QWZAFAzOceyyL6C791lGOk9SYYekxyuZkwkzhDEsoqR7fN6hmu6IfIU8hF5kt8M_Gef30wt5dUESvcTNdmQmq_L1QYA8qYO6-T0mC0zIpHpwQnANYOSZBCz1uE-vwS17MlfnUwGkPHJXWThlMZqZmQ",
e: "AQAB"
};
keystore.generate("oct", 256, props).
then(function(result) {
console.log(result);
var obj = {
header: {
alg: "HS256",
jwk: result,
nonce: "kajdfksajdf39393"
},
payload: {
"resource": "new-reg",
"contact": [
"mailto:cert-admin#example.com",
"tel:+12025551212"
]
},
secret: 'has a van',
};
const signature = jws.sign(obj);
console.log(signature);
});
}
This actually does generate a valid JWT:
eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJvY3QiLCJraWQiOiJXeVFRNFNJV2I4SGJIM2ZrQnRLOHdnR1NibU9zaGZNeUNWODZyTWdoOTR3IiwiYWxnIjoiSFMyNTYiLCJuIjoicEs3THVUMmh4a1duWVJsMVRjdzlpQXk5LV9UcXZIcDJ3aDZFY0hxX3dnbHNObXRweEFlOWdOR1pldld1NlQyTzFhRW1QWWtneTdRMW1lS05pZmVuRnVXaWNEY1NTZW5rTU0wSkFwZmR2ZWlWcWpCQTgxRUwwWTc2VDhpMkpvbGdnR1hiaVNhX1pSR3dHLTBGUERTSVgzSnk1bVFnT24tdC16cmhEOXlMRG4yTjd6ekZxQ0JPdHh6cnd6MUhFdE44UVdaQUZBek9jZXl5TDZDNzkxbEdPazlTWVlla3h5dVprd2t6aERFc29xUjdmTjZobXU2SWZJVThoRjVrdDhNX0dlZjMwd3Q1ZFVFU3ZjVE5kbVFtcV9MMVFZQThxWU82LVQwbUMweklwSHB3UW5BTllPU1pCQ3oxdUUtdndTMTdNbGZuVXdHa1BISlhXVGhsTVpxWm1RIiwiZSI6IkFRQUIifSwibm9uY2UiOiJrYWpkZmtzYWpkZjM5MzkzIn0.eyJyZXNvdXJjZSI6Im5ldy1yZWciLCJjb250YWN0IjpbIm1haWx0bzpjZXJ0LWFkbWluQGV4YW1wbGUuY29tIiwidGVsOisxMjAyNTU1MTIxMiJdfQ.RiHTdM_k1eLUJaGx4b59w8-hEQ-J0SpZjPIeGWhh1yg
However, when I try to POST it to the new registration endpoint, I get the following error:
{ "type": "urn:acme:error:malformed", "detail": "Parse error reading JWS", "status": 400 }
The testing code is a collection of code snippets I've put together after Googling this for a few hours. I understand there are LetsEncrypt servers I can run, but don't want to do that. I want to actually generate the requests and callbacks directly in Node.js because I want to run all this from AWS Lambda functions (no servers involved here).
I did find one example of a JWT token that actually seems to work, sort of. I say "sort of" because the response from this example is:
{ "type": "urn:acme:error:badNonce", "detail": "JWS has invalid anti-replay nonce 5H63XwyOHKpAETFpHR8stXSkhkqhlAY1xV7VsCnOrs", "status": 400}
This at least tells me the JWT token is being parsed and the Nonce is being looked at. When I decode this JWT, I see this:
It looks like this guy used RSA 256 to create this JWT. I'm not sure where the values "e" and "n" came from?
How do I recreate the above working sample with Node.JS / Jose?
I think the answer here is to just use the letsencrypt node.js NPM package. No need to develop ACME protocol from scratch, as this library seems to do it.