Failing signature validation of JWT tokens from Azure AD - azure

By following this MS info about client credential grants, I got a JWT token:
eyJ0eXAiOiJKV1QiLCJub25jZSI6InBOWU1CVkZKcFAxVjJ1VTFaUnM1V3NOLVhyeDhjXzhWWjU5RWxhTFZBeDAiLCJhbGciOiJSUzI1NiIsIng1dCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSIsImtpZCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8wMmRhZjUwZC00ZDFlLTRhOTUtYTI0YS1jOTg3MDUzNDBlYzAvIiwiaWF0IjoxNTg0NzIwNjk3LCJuYmYiOjE1ODQ3MjA2OTcsImV4cCI6MTU4NDcyNDU5NywiYWlvIjoiNDJOZ1lIaTZxVjRxS0xoLzRteUd0UzBmb3lLWEFRQT0iLCJhcHBfZGlzcGxheW5hbWUiOiJSc2tUZXN0QXBwIiwiYXBwaWQiOiJlMjcwYmNkNC00ZDU0LTRiN2MtYWFmYy1hYjUyZWZmMjA5N2UiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8wMmRhZjUwZC00ZDFlLTRhOTUtYTI0YS1jOTg3MDUzNDBlYzAvIiwib2lkIjoiMGQ3MDU2NWItZWQzMi00ZGMzLWJmMTYtOWFjMTRjOTVlYzJiIiwic3ViIjoiMGQ3MDU2NWItZWQzMi00ZGMzLWJmMTYtOWFjMTRjOTVlYzJiIiwidGlkIjoiMDJkYWY1MGQtNGQxZS00YTk1LWEyNGEtYzk4NzA1MzQwZWMwIiwidXRpIjoiMVVxeV9PemJJMDZOSGp6VHo1Qm9BQSIsInZlciI6IjEuMCIsInhtc190Y2R0IjoxNTg0NzE3OTU0fQ.fy2TIhXb89Ic2wuw7ysao-JzBqzpQGHD29A_X-JrjeEXOTvO5AB75tn0G1zV69vYkg2hEMsv3Dej5pNJ82w8NETRXMEEhC6ke9-URk0uKzWJ_ZzxNrL9I1eD8N4UUhqXeJifYE9gatDRqSfFmEn5eQbRVgEegJMmpqb7DMeUH8pOkTZLKzFHjSmJATx2eQBkW1PiMEL5u-QuIiZohObxficQ8PvK-IgE2V_LcTuVnEVXBrgYfZllPesQTP9-fjV_iGnM5gwcnVhwqNdpk9Ws-Vz2XDgRRCDu604IbYv3SvA9JhbKByo0CPreMfhqjrP5l_0Rm_bJeGj9iJrql5Jj3w
When I simply copy-paste it to jwt.io, the token is correctly decoded but its signature validation fails.
So I suspected jwt.io not to work properly and wanted to verify the token myself.
The token's header:
{
"typ": "JWT",
"nonce": "pNYMBVFJpP1V2uU1ZRs5WsN-Xrx8c_8VZ59ElaLVAx0",
"alg": "RS256",
"x5t": "YMELHT0gvb0mxoSDoYfomjqfjYU",
"kid": "YMELHT0gvb0mxoSDoYfomjqfjYU"
}
According to this description where the Azure public keys/certificates are available I fetched them and the key with kid=YMELHT0gvb0mxoSDoYfomjqfjYU is there:
{
"kty":"RSA",
"use":"sig",
"kid":"YMELHT0gvb0mxoSDoYfomjqfjYU",
"x5t":"YMELHT0gvb0mxoSDoYfomjqfjYU",
"n":"ni9SAyu9EsltQlV7Jo3wMUvcpYb4mmfHzV4IsDZ6NQvJjtQJuhsfqiG86VntMd76R44kCmkfMGvtQRA2_UmnVBSSLxQKvcGUqNodH7YaMYOTmHlbOSoVpi3Ox2wj6cWvhaTTm_4xzJ3F0yF0Y_aRBMxSCIwLv3nTMRNe74k4zdBnhL7k5ObOY_vUGt_5-sPo6BXoV7oov4Ps6jeyUdRKtqVZSp5_kzz16kPh1Ng_2tn4vpQimNbHRralq8rNM_gOLPAar6v7mL_qsqpgx-48e5ENFxikbB-NzAmLll1QSkzciu2rCjFGH4j_-bCHr7FxUNDL_E0vMFVDFw8SUlYMgQ",
"e":"AQAB",
"x5c":[
"MIIDBTCCAe2gAwIBAgIQG4GFMDOjD7lKSdsgshqQ/DANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTIwMDIwNTAwMDAwMFoXDTI1MDIwNDAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4vUgMrvRLJbUJVeyaN8DFL3KWG+Jpnx81eCLA2ejULyY7UCbobH6ohvOlZ7THe+keOJAppHzBr7UEQNv1Jp1QUki8UCr3BlKjaHR+2GjGDk5h5WzkqFaYtzsdsI+nFr4Wk05v+McydxdMhdGP2kQTMUgiMC7950zETXu+JOM3QZ4S+5OTmzmP71Brf+frD6OgV6Fe6KL+D7Oo3slHUSralWUqef5M89epD4dTYP9rZ+L6UIpjWx0a2pavKzTP4DizwGq+r+5i/6rKqYMfuPHuRDRcYpGwfjcwJi5ZdUEpM3IrtqwoxRh+I//mwh6+xcVDQy/xNLzBVQxcPElJWDIECAwEAAaMhMB8wHQYDVR0OBBYEFHssLV3w8SFEdZk03/TJwDfWQ6mRMA0GCSqGSIb3DQEBCwUAA4IBAQAh9iGtY+wKAMrYYLCU8uRZnUY9f+s936HhZdnJfVCuJM7y3fIbzvPO0T0dMHLz++ba0rkptoe+HjZaNA7vVwzdEtAdNff0wFef470sb+kxPi64PZK/IhtqBEwEvy090ZwGsZqM/Ut9QxFH21/t/wcz0wUBc6QGGxgWr1T/Qfzlemnz5DxuHaKQdiafz6yrwGyVjmaRkjMqeqhQy3J0nNoJNbofopSnnGH0g5IWBJBJPBk7k8RaliY0i+GwTliCgiI59ZPt1dS1+EXfNS06v1+TjTe1tPHyGot03i+iIA3WJk3REgT14y7Rhl94htzmMFmrlGNioXlfLFx9fDJQkJfz"
],
"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0"
}
Next I extract the public key from the certificate:
openssl x509 -in ms_signing_cert4.pem -pubkey -noout
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAni9SAyu9EsltQlV7Jo3w
MUvcpYb4mmfHzV4IsDZ6NQvJjtQJuhsfqiG86VntMd76R44kCmkfMGvtQRA2/Umn
VBSSLxQKvcGUqNodH7YaMYOTmHlbOSoVpi3Ox2wj6cWvhaTTm/4xzJ3F0yF0Y/aR
BMxSCIwLv3nTMRNe74k4zdBnhL7k5ObOY/vUGt/5+sPo6BXoV7oov4Ps6jeyUdRK
tqVZSp5/kzz16kPh1Ng/2tn4vpQimNbHRralq8rNM/gOLPAar6v7mL/qsqpgx+48
e5ENFxikbB+NzAmLll1QSkzciu2rCjFGH4j/+bCHr7FxUNDL/E0vMFVDFw8SUlYM
gQIDAQAB
-----END PUBLIC KEY-----
And lastly I use the Python PyJWT library to verify the signature:
import jwt
token = 'eyJ0eXAiOiJKV1QiLCJub25jZSI6InBOWU1CVkZKcFAxVjJ1VTFaUnM1V3NOLVhyeDhjXzhWWjU5RWxhTFZBeDAiLCJhbGciOiJSUzI1NiIsIng1dCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSIsImtpZCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8wMmRhZjUwZC00ZDFlLTRhOTUtYTI0YS1jOTg3MDUzNDBlYzAvIiwiaWF0IjoxNTg0NzIwNjk3LCJuYmYiOjE1ODQ3MjA2OTcsImV4cCI6MTU4NDcyNDU5NywiYWlvIjoiNDJOZ1lIaTZxVjRxS0xoLzRteUd0UzBmb3lLWEFRQT0iLCJhcHBfZGlzcGxheW5hbWUiOiJSc2tUZXN0QXBwIiwiYXBwaWQiOiJlMjcwYmNkNC00ZDU0LTRiN2MtYWFmYy1hYjUyZWZmMjA5N2UiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8wMmRhZjUwZC00ZDFlLTRhOTUtYTI0YS1jOTg3MDUzNDBlYzAvIiwib2lkIjoiMGQ3MDU2NWItZWQzMi00ZGMzLWJmMTYtOWFjMTRjOTVlYzJiIiwic3ViIjoiMGQ3MDU2NWItZWQzMi00ZGMzLWJmMTYtOWFjMTRjOTVlYzJiIiwidGlkIjoiMDJkYWY1MGQtNGQxZS00YTk1LWEyNGEtYzk4NzA1MzQwZWMwIiwidXRpIjoiMVVxeV9PemJJMDZOSGp6VHo1Qm9BQSIsInZlciI6IjEuMCIsInhtc190Y2R0IjoxNTg0NzE3OTU0fQ.fy2TIhXb89Ic2wuw7ysao-JzBqzpQGHD29A_X-JrjeEXOTvO5AB75tn0G1zV69vYkg2hEMsv3Dej5pNJ82w8NETRXMEEhC6ke9-URk0uKzWJ_ZzxNrL9I1eD8N4UUhqXeJifYE9gatDRqSfFmEn5eQbRVgEegJMmpqb7DMeUH8pOkTZLKzFHjSmJATx2eQBkW1PiMEL5u-QuIiZohObxficQ8PvK-IgE2V_LcTuVnEVXBrgYfZllPesQTP9-fjV_iGnM5gwcnVhwqNdpk9Ws-Vz2XDgRRCDu604IbYv3SvA9JhbKByo0CPreMfhqjrP5l_0Rm_bJeGj9iJrql5Jj3w'
pub_key = b'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAni9SAyu9EsltQlV7Jo3w\nMUvcpYb4mmfHzV4IsDZ6NQvJjtQJuhsfqiG86VntMd76R44kCmkfMGvtQRA2/Umn\nVBSSLxQKvcGUqNodH7YaMYOTmHlbOSoVpi3Ox2wj6cWvhaTTm/4xzJ3F0yF0Y/aR\nBMxSCIwLv3nTMRNe74k4zdBnhL7k5ObOY/vUGt/5+sPo6BXoV7oov4Ps6jeyUdRK\ntqVZSp5/kzz16kPh1Ng/2tn4vpQimNbHRralq8rNM/gOLPAar6v7mL/qsqpgx+48\ne5ENFxikbB+NzAmLll1QSkzciu2rCjFGH4j/+bCHr7FxUNDL/E0vMFVDFw8SUlYM\ngQIDAQAB\n-----END PUBLIC KEY-----'
print(jwt.decode(token, pub_key, algorithms=['RS256']))
Results in
jwt.exceptions.InvalidSignatureError: Signature verification failed
I tried to generate on my PC my own JWT, also signed by RS256 - passes both on jwt.io and PyJWT. Same for a sample token I found somewhere on the web. Just cannot handle tokens issued by AAD.
What am I missing with these AAD tokens?
Many thanks.

Your token is correct. It is just a specific token for graph api. You will see a nonce in Jwt.Header. This means you need special processing. Normal processing will fail.
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.
See the details here.
Reference:
Can not validate signature

Yes indeed. This nonce is in V2, the addition to the V1 token.
I used to validate V1 using this snippet:
var signatureKey = signatureKeyIdentifier.X509CertificateChain.First();
var certificate = new X509Certificate2(signatureKey.ToBytesFromBase64URLString());
var rsa = certificate.GetRSAPublicKey();
var data = string.Format("{0}.{1}", header, payload).ToBytes();
var isValidSignature = rsa.VerifyData(
data,
signature.ToBytesFromBase64URLString(),
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
But now this validation will fail.
Please note that I am creating a certificate as provided by header.
The nonce has nothing to do with signature, it is just a number that changes for each request/token to make sure no one is replaying that valid token. Validating the nonce is by saving all of them in a database and check uniqueness.
I think we need to find out the changes in calculating the signation. are they still using the same padding? same Algo?
The way I am using to validate now is to send that token to the same active directory to get data, then AD will give the data ONLY upon valid token.

Related

Client Assertion verify the signature failed

I have configured app registration with client certificate .cer file in azure
Using jwt.io to create client assertion, and requested access token as in below screenshot
There were few posts about issues with key not found, but in this scenario key Is not able to generate signature. unable to understand the issue here. I used private key and public key in jwt.to to generate client assertion.
Header in JWt used base64 thumbprint value-
{
"alg": "RS256",
"typ": "JWT",
"x5t": "hfbTrE31AcSETk1BFhovA1w1SMc="
}
However, signature is verified in jwt.io.
TIA
I have figured this out, its issue in Private/public key. Extract Private key as PKCS #8 to use in building jwt assertion.
Verify Signature gives error with encrypted Private key, use decrypted private key

How to sign a JWT token for OAuth client credentials for Office 365 in Java / JJWT? "Client assertion contains an invalid signature"

I am following this guide to the client credentials flow and this guide to the JWT token needed. The ultimate objective is to access calendar information through the Microsoft Graph API. Calls to the Graph API need to be authenticated with an access token which is obtained from the Microsoft token endpoint.
But calling the token endpoint itself requires some form of authentication, which can be either by shared secret or by a certificate-signed JWT token. The shared secret approach is well-documented and appears to work fine, except that the Graph service I am trying to call rejects the access token as not secure enough - it seems a certificate-signed JWT token is required. I have been unable to find any Java examples of this second approach, and what I have implemented so far following the above guides doesn't work.
Using JJWT, the code for generating the token looks like:
PrivateKey key = loadPrivateKey();
String jwt = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "RS256")
.setHeaderParam("x5t", "A7...89")
.setSubject(clientId)
.setExpiration(new Date(System.currentTimeMillis() + 200000))
.setIssuer(clientId)
.setNotBefore(new Date())
.setAudience("https://login.microsoftonline.com/" + tenantId + "/oauth2/token")
.setId(UUID.randomUUID().toString())
.signWith(
SignatureAlgorithm.RS256,
key)
.compact();
loadPrivateKey() uses BouncyCastle classes:
KeyFactory factory = KeyFactory.getInstance("RSA");
PemObject pemObject;
PemReader pemReader = new PemReader(new StringReader(pemFileContent));
try {
pemObject = pemReader.readPemObject();
} finally {
pemReader.close();
}
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return factory.generatePrivate(privKeySpec);
I created a key pair in the apps.dev.microsoft.com console, downloaded the private key and converted it to PEM format using openssl, then pasted the PEM content into the pemFileContent variable used above. openssl calculates the same thumbprint for the PEM version as the MS application console.
I use the JWT token in a call to a Retrofit service:
default Call<APIToken> getAccessToken(String tenantId, String clientId, String clientAssertion)
{
return this.getAccessToken(
tenantId,
clientId,
clientAssertion,
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"https://graph.microsoft.com/.default",
"client_credentials");
}
#FormUrlEncoded
#Headers({
"Host: login.microsoftonline.com",
"Content-Type: application/x-www-form-urlencoded"
})
#POST("/{tenant_id}/oauth2/token")
Call<APIToken> getAccessToken(
#Path("tenant_id") String tenantId,
#Field("client_id") String clientId,
#Field("client_assertion") String clientAssertion,
#Field("client_assertion_type") String clientAssertionType,
#Field("scope") String scope,
#Field("grant_type") String grantType);
Everything looks good in the debugging output:
header={typ=JWT, alg=RS256, x5t=A7...89},body={sub=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, exp=1517656715, iss=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, nbf=1517656515, aud=https://login.microsoftonline.com/87ad3067-2703-49ea-8cd2-a094fc3ee413/oauth2/token, jti=4fa5db4f-76b7-4122-9c5a-092ac74fd0fa},signature=....342 characters....
But the response is 401 Unauthorized, with the error report:
{"error":"invalid_client","error_description":"AADSTS70002: Error validating credentials. AADSTS50012: Client assertion contains an invalid signature. [Reason - The key was not found., Thumbprint of key used by client: '03B....3D', Configured keys: [Key0:Start=02/03/2018, End=12/31/2099, Thumbprint=lQ...M4;]]\r\nTrace ID: b4775394-51cc-4a4a-b927-50bd05421100\r\nCorrelation ID: f9264339-9b85-4942-9404-af941aa0331c\r\nTimestamp: 2018-02-03 11:15:18Z","error_codes":[70002,50012],"timestamp":"2018-02-03 11:15:18Z","trace_id":"b4775394-51cc-4a4a-b927-50bd05421100","correlation_id":"f9264339-9b85-4942-9404-af941aa0331c"}
I am at a loss as to where to look for the problem. Neither of the thumbprint values mentioned in the error correspond to the thumbprint of the private key. Others have reported this error message when using the wrong signature algorithm, but RS256 is the documented algorithm to use. I'm looking for either a pointer to what I'm getting wrong, or a working example in Java (I'm not fussy about which libraries are used) of the client credentials flow using a signed JWT token (I have found examples using a shared secret, and they work OK, but the MS Graph API rejects the token as not being secure enough).

Invalid signature while validating Azure ad access token

I am getting invalid signature while using jwt.io to validate my azure ad access token (will shift to scala code after the manual checking).
I am using curl to generate the access token:
curl -s -X POST https://login.microsoftonline.com/<tenant id>/oauth2/token -d grant_type=password -d username=$username -d password=$pass -d resource=$resID -d client_id=$id -d client_secret=$key
While it is giving me the access token, the response doesnt contain the "Id_token". Not sure why.
I am wrapping the public key from https://login.microsoftonline.com/common/discovery/keys with BEGIN and END certificate. (as mentioned in https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx)
I am not sure what else is missing.
The decoded header is as follows:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "9FXDpbfMFT2SvQuXh846YTwEIBw",
"kid": "9FXDpbfMFT2SvQuXh846YTwEIBw"
}
Warning: You are invoking a flow which is unsupported, and will be removed in the near future.
The Resource Owner Password Credentials Grant flow (grant_type=password) is not supported in Azure AD with confidential clients (i.e. web app/web API, where there exists a client_secret). If you are confident your scenario requires the Resource Owner flow (very few scenarios actually warrant the risks introduced by this flow), then you should be invoking it with a client_id registered for a native client app (a public client). Alternatively, you should be invoking an interactive sign-in (if you are signing in actual human users), or pursuing the Client Credentials Grant flow (if this is a daemon/unattended service).
You are not getting an ID Token because the flow you've invoked (the OAuth 2.0 Resource Owner Password Credentials Grant flow), does not define any concept of an ID Token, or an id_token response. In other words: you haven't asked for one.
If you add scope=openid to your Authorization Request, you'll be hinting to Azure AD that you're more or less interested in knowing stuff about the person who signed in, and you'll get an unsigned OpenID Connect ID Token in the response.
To https://jwt.io to verify the claims:
Ensure this is a token intended for you. (i.e. don't expect to be able to decode and verify a token that was not intended for you (where "you" are the resource in the Authorization Request, and the aud in the token claims).
Ensure you've selected the correct signing algorithm (RS256).
Ensure you're checking against the key with which the token was signed (use the kid header value from the JWT as a hint).
Ensure the certificate ends in -----END CERTIFICATE----- (I've found jwt.io doesn't care too much about how it starts.
Double-check your copy-pasting, it's easy to accidentally pick up extra characters.

Node.js authentication server for Firebase Admin SDK - JWT validation issue

I am working on a project where we are going to be using different services in a microservice architecture, and we would like to also use some Firebase services. I am working on an auth server that is going to mint custom JWT's for use in both Firebase, as well as the other API projects.
We would like to use the Firebase Auth SDK to easily integrate with FB, Google, Twitter etc, but we need to enrich the user's token with more data. Therefore, my thought process is that I'd create a Node.JS auth server that uses the Firebase Admin SDK to do this. The flow would be as follows:
User logs in with favourite provider on client
If login is succesful, the user receives a JWT from Firebase. This is sent to the auth server for validation
If the auth server can validate the token using the admin SDK, create a new custom token enriched with more data, and return this new custom token to the client
Have client re-authenticate with the new custom token, and use it for communication with both Firebase as well as our other API projects (which will mainly be in .NET Core)
Step 1-3 works fine. The problem arises when trying to verify the custom token on the other services.
TL;DR : There are two questions inhere:
When validating custom tokens issued using the Firebase Node.JS Admin SDK, what should I use as the public key? A key extracted from Google's exposed JWK's, or a key extracted from the private key that is used to sign?
In case of the JWK approach, how should I construct the custom token with a kid header?
First, I am in doubt of the proper way to verify it. (Please excuse me, I'm not that experienced creating OAuth flows.) The algorithm used is RS256, so I should be able to verify the token using a public key. As I see it, there are two ways to get this key:
Extract the public key from the private key and verify using this. I can do this and verify successfully on a test endpoint on my auth server, however I feel this is the incorrect way to do it
The other, and more correct way I think, is to use the values from the token to find the JWK's on Google's "/.well-known/openid-configuration/" endpoint for my project, , i.e.
https: //securetoken.google.com/[PROJECT ID]/.well-known/openid-configuration
to retrieve the exponent and modulus for the correct kid (key ID) and create the public key from those.
The token generated from the admin SDK by doing
admin.auth().createCustomToken(uid, additionalClaims).then(function(customToken)
with some custom claims looks something like this:
headers:
{
"alg": "RS256",
"typ": "JWT"
}
payload:
{
"claims": {
"premiumAccount": true,
"someRandomInnerObject": {
"something": "somethingRandom"
}
},
"uid": "<uid for the user>",
"iat": 1488454663,
"exp": 1488458263,
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iss": "firebase-adminsdk-le7ge#<PROJECT ID>.iam.gserviceaccount.com",
"sub": "firebase-adminsdk-le7ge#<PROJECT ID>.iam.gserviceaccount.com"
}
I can't seem to get method 2 to work, though. One problem is that the generated token does not have a kid header, and so does not conform to the OpenID spec (AFAIK), which leads to one of two options:
Go with the first approach above. This leads to problems though - if I for some reason need to revoke or reset the private key on the auth server, I need to do it and deploy the changes on all the other services too, making the solution less dynamic and more error-prone.
Generate a similar token manually using one of the libs mentioned at jwt.io, and add the kid from the original Firebase ID token to it's headers.
Problems with number 2:
What should I put as iss, aud and sub, then? The same values as the admin SDK does? If so, isn't that 'cheating', as they are no longer the issuer?
I've tried it (generating a similar copy of the token, but adding the kid of the original token), and I can't seem to verify the generated token using the created PEM key for the kid.
The way I do the latter is this (following a blog guide on the subject):
Go to https://www.googleapis.com/service_accounts/v1/jwk/securetoken#system.gserviceaccount.com and retrieve the modulus (n) and exponent (e) for the relevant kid
Generate the public key using a lib (rsa-pem-from-mod-exp)
Use the key to verify using the 'official' jwt lib
The above results in a public key as such:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAxXpo7ChLMnv1QTovmm9DkAnYgINO1WFBWGAVRt93ajftPpVNcxMT
MAQI4Jf06OxFCQib94GyHxKDNOYiweVrHVYH9j/STF+xbQwiPF/8L7+haC2WXMl2
tkTgmslVewWuYwpfm4CoQFV29OVGWCqwEcbCaycWVddm1ykdryXzNTqfzCyrSZdZ
k0yoE0Q1GDcuUl/6tjH1gAfzN6c8wPvI2YDhc5gIHm04BcLVVMBXnC0hxgjbJbN4
zg2QafiUpICZzonOUbK6+rrIFGfHpcv8mWG1Awsu5qs33aFu1Qx/4LdMAuEsvX9f
EmFZCUS8+trilqJbcsd/AQ9eOZLAB0BdKwIDAQAB
-----END RSA PUBLIC KEY-----
Two things seem to be wrong. One is that the key is different from the one I can extract from the private key. The other is that the one I extract from the private key has these comments instead:
-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----
with no 'RSA'. Does this matter? In any case, it doesn't verify.
Finally, did I misunderstand the OpenID flow completely? Are the JWKs generated from a private key that I need as well to verify my JWTs? Should I expose my own JWKs on my auth server for the other services to contact and use instead of Google's? I'm a bit confused as to what the Firebase Admin SDK does and doesn't do, I think :-)
I know this is a lot of questions, but I think they're all related.
Some resources I've relied on in my research (besides the official admin sdk docs ofcourse):
jwt.io
Is it still possible to do server side verification of tokens in Firebase 3?
https://ncona.com/2015/02/consuming-a-google-id-token-from-a-server/
https://stackoverflow.com/a/42410233/1409779
https://andrewlock.net/a-look-behind-the-jwt-bearer-authentication-middleware-in-asp-net-core/
After re-authenticating the Firebase client SDK with the custom token, the client actually generates a new ID token with the claims from the custom token. This ID token is what you should use to verify requests made to your different microservices (documented here). So yes, your original ID token is discarded, but a new one is created in its place. And that ID token will be automatically refreshed every hour. So, you should be able to just call user.getToken() to get a valid ID token whenever you need it. That method handles all the caching on your behalf.

Where are JWT tokens stored on the server and other related questions

As the title suggests, where are JWT tokens stored on the server side? in database or in memeory? I understand the implementation can vary due to different requirements, but just in general where would you store it?
If I want to provide a very basic token authentication server, meaning upon receiving a username and password via a POST request, I would like to return a token. In this case, how is a token generated with a very basic algorithm work differently than a jwt token?
With a token generated by a simple algorithm:
it does not contain payload
its value is not computed based on the username and password, thus it cannot be rehashed back to anything meaningful
In this case, is there still value to use JWT?
Thanks!
client needs to store it, on server storage is not required.
JWT have all the claims in itself and is signed by the server as well. On receipt, server checks for the signature and reads the claims. It does not match it against a stored value. That is the whole point of using JWT against access tokens.
Look at how a JWT is structured.
You don't need to store token on server side.
You should store a private key ( any string of your choice)at server preferably as environment variable.The jsonwebtoken provided method use this private key to generate a token to pass to client.
Client has to store this token at client side so that it can pass this token to subsequent request to server in header.
Server would extract the token value from header and validate it using private key by calling a method of jsonwebtoken.If token is not modified by any means then validate will succeed.
Jwt token is not required to be stored but the "secret-key" needs to be stored.
The structure of jwt is header.payload.signature where signature is generated as below by the server :
signature = HS256(base64Header + '.' + base64Payload, 'mysecret')
So in essense:
1.header.payload.signature is sent to client on first sign in
2.client return back header.payload.signature in subsequent api call
3.server decodes it for verification as below:
base64Header, base64Payload, signature = token.split('.')
header = base64Decode(base64Header)
payload = base64Decode(base64Payload)
serverComputedSignature = HS256(base64Header + '.' + base64Payload,
'mysecret')
if serverComputedSignature != signature:
print('FAILED')
A token is a generic term. JWT defines the structure of a token which contains the below three parts.
1. header
2. payload
3. signature
Storing JWT or any other format of token is driven by the business need.
If the content of the JWT has to be used/validated for any reason then it can be stored in a DB or any other storage.
If the need is to validate the signature then there is no reason to store the issued JWT.

Resources