I have a simple module that creates JWT tokens using RsaSha256 (RS256) asymmetric encryption.
let getSigningCredentials (rsa:RSA) (algo)=
try
let key = RsaSecurityKey(rsa)
let signingCredentials = SigningCredentials(key, algo)
signingCredentials.CryptoProviderFactory <- CryptoProviderFactory(CacheSignatureProviders = false)
Ok signingCredentials
with ex ->
Error ex
let getSigningCredentialsSha256 (rsa:RSA) =
getSigningCredentials rsa SecurityAlgorithms.RsaSha256
let createJwtSecurityToken jwtSecurityTokenRecord =
try
Ok
(JwtSecurityToken(
issuer = jwtSecurityTokenRecord.Issuer,
signingCredentials = jwtSecurityTokenRecord.SigningCredentials,
claims = jwtSecurityTokenRecord.Claims,
notBefore = Nullable jwtSecurityTokenRecord.NotBefore,
expires = Nullable jwtSecurityTokenRecord.Expires))
with ex ->
Error ex
This works well, I can create tokens. I can load them into jwt.io (i know, not production token) and verify with the public part of the keypair. I use OpenSSL to generate the keypair so I need to convert the private key to Pkcs8PrivateKey before importing it into the RSA object.
So everything works just fine. Now, I would like to verify the JWT token with the public key using F# (C# code is fine too).
Here is where it gets hairy.
I could not find any documentation on how to do so.
The only validation method I was able to find uses the signing key (private key) for the verification.
let validateJwtToken (rsa:RSA) (tokenString:string) =
try
let tokenValidationParameters =
TokenValidationParameters (
ValidateIssuerSigningKey = true,
IssuerSigningKey = RsaSecurityKey rsa,
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
)
Ok (JwtSecurityTokenHandler().ValidateToken (tokenString, tokenValidationParameters, ref null))
with ex ->
Error ex
Is there a way / method to verify a JWT token with the public key?
I am using System.IdentityModel.Tokens.Jwt and Microsoft.IdentityModel.Tokens.
Related
i want to create a JWT in a scala application for talking to the Apple's AppStore Connect api. i'm following the guide here
i'm getting an invalid signature on jwt.io when creating a JWT with the below code. a request to appstore connect results in a 401
i can verify that the JWT encodes the header and payload correctly on http://jwt.io
looking at this library, i think i'm selecting the correct curve algorithm:
After creating the token, one must sign it with an Apple-provided private key, using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm, or ES256.
i'm not sure what is wrong - maybe i'm not generating the S value correctly?
def generateJWT(): String = {
val privateKeyBytes: Array[Byte] =
Base64.getDecoder.decode("base64 representation of private key")
val S = BigInt(privateKeyBytes)
val curveParams = ECNamedCurveTable.getParameterSpec("P-256")
val curveSpec: ECParameterSpec =
new ECNamedCurveSpec("P-256", curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
val privateSpec = new ECPrivateKeySpec(S.underlying, curveSpec)
val privateKey =
KeyFactory.getInstance("EC").generatePrivate(privateSpec)
val unixTimestamp: Long = Instant.now.getEpochSecond
val fiveMins = unixTimestamp.toInt + 300
val header = Map[String, Object](
"kid" -> "appstore connect Key Identifier",
"typ" -> "JWT",
"alg" -> "ES256"
)
val payload = Map[String, Object](
"iss" -> "issuer ID from the API Keys page in App Store Connect",
"aud" -> "appstoreconnect-v1",
"exp" -> new Integer(fiveMins)
)
Jwts
.builder()
.setHeaderParams(header.asJava)
.setClaims(payload.asJava)
.signWith(SignatureAlgorithm.ES256, privateKey)
.compact()
}
I can suggest two things to try out:
JwtBuilder signWith(SignatureAlgorithm var1, Key var2) is deprecated. Can you try using JwtBuilder signWith(Key var1, SignatureAlgorithm var2) , and see if that succeeds?
If not, you can try using bountycastle , which does work for me. Following is the code snippet for getting the private key.
def getPrivateKey(): PrivateKey = {
val pemParser = new PEMParser(new FileReader(<Your Apple-AuthKey file with extension .p8>))
val converter = new JcaPEMKeyConverter
val privateKeyInfo = pemParser.readObject.asInstanceOf[PrivateKeyInfo]
val pKey = converter.getPrivateKey(privateKeyInfo)
pemParser.close()
pKey
}
I have created a service account and downloaded my JSON Credential on Google Cloud Platform. I need to make REST POST call in .NET to DialogFlow Service API. At this moment, I can do it only with a generated token in PowerShell. Since, I need to do it all from script, I need to generate a JWT to pass as my bearer in my REST call. My Problem is that the generated JWT is not honored by Google.
I get my response in PowerShell based on this doc page and I replicate sample codes from this doc page to create my JWT.
public static string GetSignedJwt(string emailClient, string
dialogueFlowServiceApi, string privateKeyId, string privateKey, string
jsonPath)
{
// to get unix time in seconds
var unixTimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// start time of Unix system
var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// adding milliseconds to reach the current time, it will be used for issueAt time
var nowDataTime = origin.AddSeconds(unixTimeSeconds);
// one hour after the current time, it will be used for expiration time
var oneHourFromNow = nowDataTime.AddSeconds(3600);
// holder of signed json web token that we will return at the end
var signedJwt = "";
try
{
// create our payload for Jwt
var payload = new Dictionary<string, object>
{
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
// create our additional headers
var extraHeaders = new Dictionary<string, object>
{
{"kid", privateKeyId}
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
}
catch (Exception e)
{
Console.WriteLine(e);
// return null if there has been any error
return null;
}
finally
{
Console.WriteLine(signedJwt);
}
return signedJwt;
}
Notice that, it is needed to be signed in RSA256 by passing public and private keys, as Google did it in Java sample snippet, however, my equivalent in .Net gives me only Object reference not set to an instance of an object when I use that algorithm:
var key = RSA.Create(privateKey);
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
Besides of correct keys, I am using https://dialogflow.googleapis.com/google.cloud.dialogflow.v2beta1.Intents as dialogFlow service API key.
I expect it that my generated JWT gets accepted, however it is rejected by Google.
1) You are using the wrong algorithm
Change this line of code:
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
To this:
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
2) For the JWT headers:
var additional_headers = new Dictionary<string, object>
{
{ "kid", privateKeyId },
{ "alg", "RS256" },
{ "typ", "JWT" }
};
3) Your JWT Payload does not include a scope. I am not sure which scope you need but here is an example. Add this to the payload before creating the JWT:
string scope = "https://www.googleapis.com/auth/cloud-platform";
var payload = new Dictionary<string, object>
{
{"scope", scope},
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
4) For most Google APIs (not all) you also need to exchange the Signed JWT for a Google OAuth Access Token:
public static string AuthorizeToken(string token, string auth_url)
{
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var content = new NameValueCollection();
// Request a "Bearer" access token
content["assertion"] = token;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var response = client.UploadValues(auth_url, "POST", content);
return Encoding.UTF8.GetString(response);
}
The Authorization URL for above:
string auth_url = "https://www.googleapis.com/oauth2/v4/token";
Sorry for the multiple post about the same issue!
I'm trying to upload a self signed sertificate to application manifest created on Microsoft Registration Portal but I have some issues which I don't completly understand why, According to this answer, it's very much possible to upload the certificate using DELEGATED PERMISSIONS however I don't see the reason why I can't use Application Permissions since I only need the AccessToken and I get that with the client_credential grant flow,
Below is the code that I have tried but when retrieving the token with client_credential grant flow, I get stuck att var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
and when trying to use the code given to my by Tom Sung in the previous post, the applications exits with error "must have client_credentil or client_assertion in request body"
this is the code that I have tried:
private static async Task<string> GetAppTokenAsync(string graphResourceId, string tenantId, string clientId, string userId)
{
string aadInstance = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authenticationContextt =
new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/oauth2/token");
AuthenticationResult result =
await authenticationContextt.AcquireTokenAsync(graphResourceId,
clientCredential);
//token is acquiered and gets stuck
var e = result.AccessToken;
//Tom Suns code
IPlatformParameters parameters = new PlatformParameters(PromptBehavior.SelectAccount);
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance);
var authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientId, new Uri("http://localhost"), parameters, new UserIdentifier(userId, UserIdentifierType.UniqueId));
//exits with error
return authenticationResult.AccessToken;
}
try
{
var graphResourceId = "https://graph.windows.net";
var userId = "****";
//used to test if token is acquired
//var tokennn = await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId);
var servicePointUri = new Uri(graphResourceId);
var serviceRoot = new Uri(servicePointUri, tenant);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId));
AsymmetricKeyParameter myCAprivateKey = null;
//generate a root CA cert and obtain the privateKey
X509Certificate2 MyRootCAcert = CreateCertificateAuthorityCertificate("CN=OutlookIntegration", out myCAprivateKey);
//add CA cert to store
addCertToStore(MyRootCAcert, StoreName.Root, StoreLocation.LocalMachine);
var expirationDate = DateTime.Parse(MyRootCAcert.GetExpirationDateString()).ToUniversalTime();
var startDate = DateTime.Parse(MyRootCAcert.GetEffectiveDateString()).ToUniversalTime();
var binCert = MyRootCAcert.GetRawCertData();
var keyCredential = new KeyCredential
{
CustomKeyIdentifier = MyRootCAcert.GetCertHash(),
EndDate = expirationDate,
KeyId = Guid.NewGuid(),
StartDate = startDate,
Type = "AsymmetricX509Cert",
Usage = "Verify",
Value = binCert
};
//gets stuck here when using clientsecret grant type
var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
application.KeyCredentials.Add(keyCredential);
application.UpdateAsync().Wait();
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
I am now completly stuck, Anyone have any idea why it doesn't work with Application Permissions or why it gets stuck at var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
Edit 1
is it because I have my app as a web app/API that uses username and password to authenticate?
Based on my test if we want to change the keyCredential, DELEGATED PERMISSIONS is required.
If we want to update Azure AD application other properties, we could use Application Permissions.
Reference:
Azure Active Directory developer glossary
"Delegated" permissions, which specify scope-based access using delegated authorization from the signed-in resource owner, are presented to the resource at run-time as "scp" claims in the client's access token.
"Application" permissions, which specify role-based access using the client application's credentials/identity, are presented to the resource at run-time as "roles" claims in the client's access token.
I am using Postman to test OAuth 2 from a vanilla AEM install.
Postman can successfully obtain the authorization code from /oauth/authorize after I grant access:
But when it tries to use the code to obtain a token from /oauth/token it receives the following response:
HTTP ERROR: 403 Problem accessing /oauth/token. Reason: Forbidden
Powered by Jetty://
Looking in Fiddler it is doing a POST to /oauth/token with the following Name/Values in the body:
client_id: Client ID from /libs/granite/oauth/content/client.html
client_secret:
Client Secret from /libs/granite/oauth/content/client.html
redirect_uri: https://www.getpostman.com/oauth2/callback
grant_type: authorization_code
code: Code returned from previous request to oauth/authorize
Am I missing something?
Would help if you can list some code snippets on how you are building the url and fetching the token.
Here's an example of how we've implemented very similar to what you are trying to do, maybe it'll help.
Define a service like below (snippet) and define the values (host, url, etc) in OSGI (or you can also hard code them for testing purposes)
#Service(value = OauthAuthentication.class)
#Component(immediate = true, label = "My Oauth Authentication", description = "My Oauth Authentication", policy = ConfigurationPolicy.REQUIRE, metatype = true)
#Properties({
#Property(name = Constants.SERVICE_VENDOR, value = "ABC"),
#Property(name = "service.oauth.host", value = "", label = "Oauth Host", description = "Oauth Athentication Server"),
#Property(name = "service.oauth.url", value = "/service/oauth/token", label = "Oauth URL", description = "Oauth Authentication URL relative to the host"),
#Property(name = "service.oauth.clientid", value = "", label = "Oauth Client ID", description = "Oauth client ID to use in the authentication procedure"),
#Property(name = "service.oauth.clientsecret", value = "", label = "Oauth Client Secret", description = "Oauth client secret to use in the authentication procedure"),
#Property(name = "service.oauth.granttype", value = "", label = "Oauth Grant Type", description = "Oauth grant type") })
public class OauthAuthentication {
...
#Activate
private void activate(ComponentContext context) {
Dictionary<String, Object> properties = context.getProperties();
host = OsgiUtil.toString(properties, PROPERTY_SERVICE_OAUTH_HOST,new String());
// Similarly get all values
url =
clientID =
clientSecret =
grantType =
authType = "Basic" + " "+ Base64.encode(new String(clientID + ":" + clientSecret));
}
public static void getAuthorizationToken(
try {
UserManager userManager = resourceResolver.adaptTo(UserManager.class);
Session session = resourceResolver.adaptTo(Session.class);
// Getting the current user
Authorizable auth = userManager.getAuthorizable(session.getUserID());
user = auth.getID();
password = ...
...
...
String serviceURL = (host.startsWith("http") ? "": protocol + "://") + host + url;
httpclient = HttpClients.custom().build();
HttpPost httppost = new HttpPost(serviceURL);
// set params
ArrayList<BasicNameValuePair> formparams = new ArrayList<BasicNameValuePair>();
formparams.add(new BasicNameValuePair("username", user));
formparams.add(new BasicNameValuePair("password", password));
formparams.add(new BasicNameValuePair("client_id", clientID));
formparams.add(new BasicNameValuePair("client_secret",clientSecret));
formparams.add(new BasicNameValuePair("grant_type",grantType));
UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(formparams, "UTF-8");
httppost.setEntity(postEntity);
// set header
httppost.addHeader("Authorization", authType);
response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() == 200) {
if (entity != null) {
object = new JSONObject(EntityUtils.toString(entity));
}
if (object != null) {
accessToken = object.getString("access_token");
////
}
}
}
I found the answer myself and thought I'd share the process I went through as well as the answer because it might help other people new to AEM.
How to find the cause of the error:
Go to CRXDE Lite.
Select console.
Then deselect the stop button to allow new console logs to appear (this is very counter-intuitive to me).
From here I was able to see the cause of the issue:
org.apache.sling.security.impl.ReferrerFilter Rejected empty referrer header for POST request to /oauth/token
Because postman does not place a referrer in the request header I had to tell Apache Sling to allow empty request headers.
To do this:
Go to /system/console/configMgr
Open the Apache Sling Referrer Filter Config
Select the Allow Empty check box
Good way to allow this to list the allowed hosts, otherwise this is against best practices for AEM security checklist.
Its fine for development environment not for production.
I have managed to get back a JWT token from Identity Server using OAuth2 and would like to extract the claims from the token.
When I use a token decoder such as https://developers.google.com/wallet/digital/docs/jwtdecoder, I can peek inside the token and it looks fine.
However I am not sure what decrypting to use in c# in order to use the Microsoft JwtSecurityTokenHandler.ValidateToken to get back a claims identity.
In identity server, I am using a symmetric key which I have pasted for reference in my code. The JWT token is also valid.
Would really appreciate some help:
string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
string key = "ZHfUES/6wG28LY+SaMtvaeek34t2PBrAiBxur6MAI/w=";
var validationParameters = new TokenValidationParameters()
{
AllowedAudience = "urn:webapisecurity",
SigningToken = new ????
ValidIssuer = #"http://identityserver.v2.thinktecture.com/trust/changethis"
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, validationParameters);
What sort of SigningToken should I use for the validationParameters.SigningToken ??
You can use the following website to Decode the token
http://jwt.io/
or here is a code to Decode JWT Token using C#
class Program
{
static void Main(string[] args)
{
string token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
var parts = token.Split('.');
string partToConvert = parts[1];
var partAsBytes = Convert.FromBase64String(partToConvert);
var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count());
//JSON.net required
var jwt = JObject.Parse(partAsUTF8String);
Console.Write(jwt.ToString());
Console.ReadLine();
}
}
It's a BinarySecretSecurityToken - base64 decode the stringified key to use it.