I want to write e2e tests that can run in our CI environment which is tightly locked down and has no internet access.
I am using the auth0 react loginWithRedirect function which will attempt to redirect to the auth0 login on their servers and will timeout on CI.
I am able to intercept the call to /authorize in express:
app.get('/auth0/:simulation_id/authorize', middleware, (req, res) => {
const { client_id, redirect_uri, scope, state } = req.query;
Is it now possible for me to generate a mock oauth token that will be accepted by the #auth0/react client code?
Yes. Using the jsrsasign.js library, where accessKey contains a private key:
function createJwt(username, type, realm, scopes, sessionId, expires_in, audience) {
var oHeader = {
alg: 'RS256',
typ: 'JWT'
};
// Payload
var oPayload = {};
var tNow = jwt.KJUR.jws.IntDate.get('now');
var token_expiry = expires_in;
var tEnd = tNow + token_expiry;
oPayload.sub = username;
oPayload.auditTrackingId = uuidv4();
oPayload.iss = "https://mock.org/identities/v1/" + realm + "/token";
oPayload.tokenName = type;
oPayload.token_type = 'Bearer';
oPayload.authGrantId = !sessionId ? uuidv4() : sessionId;
oPayload.aud = (audience ? audience : "https://mock.org/identities/v1/" + realm + "/token");
oPayload.nbf = tNow;
oPayload.scope = scopes;
// oPayload.auth_time = tNow;
oPayload.realm = '/' + realm;
oPayload.exp = tEnd;
oPayload.expires_in = token_expiry * 100000;
oPayload.iat = tNow;
oPayload.jti = uuidv4();
var sHeader = JSON.stringify(oHeader);
var sPayload = JSON.stringify(oPayload);
var prvKey = accessKey;
var sJWT = jwt.KJUR.jws.JWS.sign("RS256", sHeader, sPayload, prvKey);
return sJWT;
}
Set the audience, subject, scp and any other properties in line with the tokens that auth0 is creating.
You also might need to mock out the jwk endpoints, but I doubt the library will care. If it doesn't work it will be due to certain properties not being available on the token.
You might need to create an id token as well, depending on your setup.
Access key is similar to this:
var accessKey = jwt.KEYUTIL.getKey("-----BEGIN PRIVATE KEY-----\n" +
"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC0g+nePEZ6aFiH\n" +
"ccnle/ryqz1lNvloPGuA5b6GeE8MrT7SATxv54zQ2LnvuRp86cd32elL0YAw3GMc\n" +
... snip ...
"Rno3R82z6SAvy9HgIMzfti5NSVgqC9nmhFEs+ChFWuboGotVV99COVJId9S/567n\n" +
"kz90cLENtD/8JTYAhLea5F/PJBJJHSvQT298ZxR4bw1vQ5Bq2FMnLSYuIOQMkQdr\n" +
"Yqt+8gXW0+3kfyb3cCyI+2HKcQ==\n" +
"-----END PRIVATE KEY-----\n");
And keep going til you've mocked out the entire IdP platform :) (I basically did the same thing with OpenAM a few years ago).
In addition to #stringy05's response; if you've got back-end services (e.g. Java, .NET, etc) that are called by your React app and need to do JWT validation, you might need to statically host your own an OpenID Configuration and JSON Web Key Set endpoints in your own environment.
The reason for this is because JWT validation (by back-end services) would still require communication to the external authorization server. With JWT-based auth this is generally limited to a single (up-front or lazy-loaded) request to get the public key for signature validation, but there could also be additional calls e.g. when the service encounters an unknown kid (key ID) on a JWT, as well as a periodical refresh interval (e.g. daily).
For Auth0 OpenID Configuration and JSON Web Key Set endpoints are hosted at the following locations:
OpenID Configuration: https://[tenant-name].[2-letter-iso-country-code].auth0.com/.well-known/openid-configuration
JSON Web Key Set: https://[tenant-name].[2-letter-iso-country-code].auth0.com/.well-known/jwks.json
These are just static JSON documents. You can probably just copy these from one of your existing tenants, modify accordingly, and then host in your own environment. The modifications you'd need to make include:
OpenID Configuration to reference local jwks_uri endpoint.
Update the key information in the JSON Web Key Set endpoint. Note: you can generate an asymetric key pair using OpenSSL, and see here for information about how to extract modulus and exponent from public key (which are required for the JWKS document). Note: in your React app, you'd need to sign the tokens with the private key that you generate.
Once you've done the above, your back-end services can reference the locally hosted OpenID Configuration and will be able to do JWT validation without communicating to an external authorization server.
Related
I am integrating a Smart on FHIR app that will be launched from within an EHR. When the user clicks a button to launch the app, we set a GUID and the current Patient ID to a database record on our FHIR server. The assumption being that given the 'Launch' scope, the OAuth server will call the appropriate API to retrieve the Patient ID given that the GUID is included in the url params.
The call to auth looks like this:
_clientID = {the unique client ID registered to our auth server}
_redirectURL = {redirect back to auth for eventual token request}
launch={the GUID value generated at start of the session and paired with the Patient ID}
_scopes = "launch patient/*.* openid profile"
state = {some opaque value}
aud = {the base URL for our FHIR server}
string url = $"{authorizeURL}?response_type=code&client_id={_clientID}&" +
$"redirect_uri={_redirectURL}&" +
$"launch={launch}&" +
$"scope={HttpUtility.UrlEncode(_scopes)}&" +
$"state={state}&" +
$"aud=https://xxx-smart.xxxxxxxxx.com";
All of this works and I end up with a json response that includes the id_token, access_token, expires_in, token_type('Bearer'). But, no 'patient'.
My assumption was that the OAuth server would call the scope 'launch/patient' on our FHIR server but no such call is being made. In fact, I created a few endpoints just for the purpose of logging and NONE of them are being called.
Here is an example of one of my FHIR Server test/log endpoints (I created few with 1 to 4 parameters):
[AllowAnonymous]
[HttpGet("{functionName}/{id}")]
public string GetPatientData3(string functionName, string id)
{
TelemetryClient telemetry = new();
telemetry.TrackEvent($"FHIR SVR GetPatientData3 {functionName} {id}");
string configJson = "0009998888";
return configJson;
}
How do I set this 'patient' context properly?
How does the OAuth server retrieve this context so I can have that patient ID appear in the json response from the ~/token call?
Further Notes:
The contents of the openid-configuration:
{"token_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported":
["client_secret_post","private_key_jwt","client_secret_basic"],
"jwks_uri":
"https://login.microsoftonline.com/xxx/discovery/v2.0/keys",
"response_modes_supported": ["query","fragment","form_post"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported":["code","id_token","code
id_token","id_token token"],
"scopes_supported":["openid","profile","email","offline_access"],
"issuer": "https://login.microsoftonline.com/xxx/v2.0",
"request_uri_parameter_supported":false,
"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo",
"authorization_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/authorize",
"device_authorization_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/devicecode",
"http_logout_supported":true,
"frontchannel_logout_supported":true,
"end_session_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/logout",
"claims_supported":
["sub","iss","cloud_instance_name","cloud_instance_host_name",
"cloud_graph_host_name","msgraph_host","aud","exp","iat",
"auth_time","acr","nonce","preferred_username",
"name","tid","ver","at_hash","c_hash","email"],
"kerberos_endpoint":
"https://login.microsoftonline.com/xxx/kerberos",
"tenant_region_scope":"NA",
"cloud_instance_name":"microsoftonline.com",
"cloud_graph_host_name":"graph.windows.net",
"msgraph_host":"graph.microsoft.com",
"rbac_url":"https://pas.windows.net"}
So, I notice that the 'patient/.' and the 'launch' scope among a whole host of others that I have are not supported according to my openid config. The only supported ones are "openid","profile","email", "offline_access".
In Azure AD, 'App Registration' > 'Expose an API' I have a list of at least 15 scopes entered there. In 'API' permissions they are all listed there as well.
One other thing to note, AzureAD does not handle scopes with a forward slash. So, launch/patient has to be entered as launch-patient. We also had to implement a proxy server to capture the ~/oauth2/v2.0/authorize request and modify the scope parameter entries to reflect this before passing on the request to the actual server.
I guess the pertinent question now is: How do the scopes that I have entered manually get supported?
What I got so far:
In a project I have an authorization server (Identity Server 4), some (let's say two) protected APIs (Api Resource) and some trusted clients (automated, no user interaction) which should access the Identity Server via the backchannel (right?). Imagine the client is a Amazon Fire TV box kind thingy.
According to what I have read so far over the last weeks a suitable flow for this scenario is the OpenID Connect Authorization Code Flow.
clients are trusted (and can maintain a secret)
Authorization Code flow supports refresh tokens (which I want to use)
the client is actually not the resource owner but requires access to the full api resource
What I have in my (theoretical) structure:
I have two API Resources (one resource for each API version)
api.v1
api.v2
I also have two series of my API clients
client.v1 supports only api v1 & should only have access to api.v1 resource
client.v2 supports api v1 & v2 and therefore should have access to both api resources
Identity Server 4 StartUp.cs configuration (so far)
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources
(
new List<ApiResource>
{
new ApiResource("api.v1", "API v1"),
new ApiResource("api.v2", "API v2")
}
)
.AddInMemoryClients
(
new List<Client>
{
new Client
{
ClientId = "client.v1",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret1".Sha256()) },
AllowedScopes = { "api.v1" }
},
new Client
{
ClientId = "client.v2",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret2".Sha256()) },
AllowedScopes = { "api.v1", "api.v2" }
}
}
);
}
The theory what I am struggling with is the authorization code part.
I want to have each client instance (again imagine it as a small box) a different authorization code allowing one instance access but deny fo another one.
Is the authorization code intended to be used for that?
And one important thing I haven't understood in all the time: CodeAndClientCredentials defines two grant types. Does this mean connecting with that requires both (code AND client credentials) or is it an one of them definition (code OR client credentials).
The Identity Server 4 code I am struggling with is:
In the code defining the client I can only find AuthorizationCodeLifetime but no field to set the authorization code itself.
It seems I can define a list of client secrets.
ClientSecrets = { new Secret("secret1".Sha256()) },
Does this mean one client Id can have multiple secrets used? Are different client secrets better suitable for my "allow one deny the other" problem?
Edit
Ok, I have re-read that and now I got it (at least a bit more): the authorization code is not defined sent by the client but the client receives it.
The authorization code flow returns an authorization code (like it says on the tin) that can then be exchanged for an identity token and/or access token. This requires client authentication using a client id and secret to retrieve the tokens from the back end
from this blog here
But how would I have to configure my Identity Server to allow one instance and deny another.
By using different client secrets? Using extension grants?
I have a Angular 4 site that I’m trying to use Microsoft Graph implicit flow to authenticate users then use token to call our APIs at another endpoint, so I use msal.js to get the access token.
After I bring the access token to my API endpoint and try to valid it, the token cannot be valid. I got a SignatureVerificationFailedException.
My understanding is that the access token is for Microsoft Graph API, not for my APIs, so I cannot valid it. (I can use it to call Graph API without problem)
How can I get a access token(not id token) using msal.js that can be used for my APIs but not Microsoft Graph? Thanks!
The reason I'm sending access token instead of id token to the API endpoint is that I want to get the puid claim from the token, which is not available for id token.
Here is what I was trying to valid the access token I got from client which is using msal.js
const string authority = "https://login.microsoftonline.com/common";
const string audience = "https://graph.microsoft.com";
string issuer = null;
string stsDiscoveryEndpoint = $"{authority}/v2.0/.well-known/openid-configuration";
List<SecurityToken> signingTokens = null;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
var config = await configManager.GetConfigurationAsync();
issuer = config.Issuer;
signingTokens = config.SigningTokens.ToList();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = issuer,
ValidateIssuer = false,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
return ExtractAuthenticatedUserFromClaimsIdentity(claimsIdentity);
}
catch (SignatureVerificationFailedException)
{
throw;
}
Thanks,
If you want to get an access token for your API rather than the Microsoft Graph API, you must specify your API as the resource in the token request.
Make sure that:
Your Web API has configured OAuth2Permission Scopes. See here. Configuring a resource application to expose web APIs
Your Client Application has selected permissions to those exposed APIs. Configuring a client application to access web APIs
Finally, make sure you use your Web API's App ID URI or App ID GUID as the resource value in your token request.
Let me know if this helps!
How can I download a file from an Autodesk A360 bucket that I created? The file is a Revit project file and I used 2-legged OAuth for authorization.
You need to use 3 legged authentication to access files from A360, because you need the approval of the user whose account you are accessing - the user is the 3rd leg.
If you are trying to access files from your own application's private bucket on OSS then you do not need a user's approval because the bucket belongs to your app and not a user.
As a side note, in case of trying to access BIM 360 files (in case that's what you are talking about) using the Data Management API, then for the time being you need to use 2 legged authentication but your app needs to be manually approved by us.
2-legged vs 3-legged authentication is covered by Augusto's webcast:
Introduction to oAuth and Data Management API
I don't get a bucket key after attempting to create a bucket in when i change v1 to v2...
see below
public static string GetBucket(string accessToken, string bucketKey, string policy)
{
// (1) Build request
var client = new RestClient();
client.BaseUrl = new System.Uri(baseApiUrl);
// Set resource/end point
var request = new RestRequest();
request.Resource = "oss/v1/buckets";
request.Method = Method.GET;
// Add headers
request.AddHeader("Authorization", "Bearer " + accessToken);
request.AddHeader("Content-Type", "application/json"); // MH: skipping this works.
// Add JSON body. in simplest form.
request.AddJsonBody(new { bucketKey = bucketKey, policy = policy });
// (2) Execute request and get response
IRestResponse response = client.Execute(request);
//TaskDialog.Show("create bucket", response.StatusDescription);
// Save response. This is to see the response for our learning.
m_lastResponse = response;
TaskDialog.Show("response", m_lastResponse.ToString());
// Get the key = bucket name
string key = "";
if (response.StatusCode == HttpStatusCode.OK)
{
JsonDeserializer deserial = new JsonDeserializer();
OssBucketsResponse bucketsResponse = deserial.Deserialize<OssBucketsResponse>(response);
key = bucketsResponse.key;
}
return key; // the bucket name
}
I am writing some code to try to get a token to use from Google in OAuth2. This is for a service account, so the instructions are here:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I keep getting this error when I post the JWT to Google:
{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }
Here is the code:
try{
var nowInSeconds : Number = (Date.now() / 1000);
nowInSeconds = Math.round(nowInSeconds);
var fiftyNineMinutesFromNowInSeconds : Number = nowInSeconds + (59 * 60);
var claimSet : Object = {};
claimSet.iss = "{{RemovedForPrivacy}}";
claimSet.scope = "https://www.googleapis.com/auth/plus.business.manage";
claimSet.aud = "https://www.googleapis.com/oauth2/v4/token";
claimSet.iat = nowInSeconds;
claimSet.exp = fiftyNineMinutesFromNowInSeconds;
var header : Object = {};
header.alg = "RS256";
header.typ = "JWT";
/* Stringify These */
var claimSetString = JSON.stringify(claimSet);
var headerString = JSON.stringify(header);
/* Base64 Encode These */
var claimSetBaseSixtyFour = StringUtils.encodeBase64(claimSetString);
var headerBaseSixtyFour = StringUtils.encodeBase64(headerString);
var privateKey = "{{RemovedForPrivacy}}";
/* Create the signature */
var signature : Signature = Signature();
signature = signature.sign(headerBaseSixtyFour + "." + claimSetBaseSixtyFour, privateKey , "SHA256withRSA");
/* Concatenate the whole JWT */
var JWT = headerBaseSixtyFour + "." + claimSetBaseSixtyFour + "." + signature;
/* Set Grant Type */
var grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
/* Create and encode the body of the token post request */
var assertions : String = "grant_type=" + dw.crypto.Encoding.toURI(grantType) + "&assertion=" + dw.crypto.Encoding.toURI(JWT);
/* Connect to Google And Ask for Token */
/* TODO Upload Certs? */
var httpClient : HTTPClient = new HTTPClient();
httpClient.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=utf-8");
httpClient.timeout = 30000;
httpClient.open('POST', "https://www.googleapis.com/oauth2/v4/token");
httpClient.send(assertions);
if (httpClient.statusCode == 200) {
//nothing
} else {
pdict.errorMessage = httpClient.errorText;
}
}
catch(e){
Logger.error("The error with the OAuth Token Generator is --> " + e);
}
Does anyone know why the JWT is failing?
Thanks so much!
Brad
The problem might be related to the fact that your StringUtils.encodeBase64() method is likely to perform a standard base64 encoding.
According to the JWT spec, however, it's not the standard base64 encoding that needs to be used, but the the URL- and filename-safe Base64 encoding, with the = padding characters omitted.
If you don't have a utility method handy for base64URL encoding, you can verify by
replacing all + with -;
replacing all / with _;
removing all =
in your base64-encoded strings.
Also, is your signature also base64-encoded? It needs to be, following the same rules as described above.
I had the same problem before and this is what was wrong:
wrong application name (project ID)
wrong service account ID (email)
The another reason for this error could be "Your service account is not activated", With gsutil installed from the Cloud SDK, you should authenticate with service account credentials.
1- Use an existing service account or create a new one, and download the associated private key.
2- Use gcloud auth activate-service-account to authenticate with the service account:
gcloud auth activate-service-account --key-file [KEY_FILE]
Where [KEY_FILE] is the name of the file that contains your service account credentials.
Link for more detail: Activate service account
This could also happen if a developer mistakenly copies, edits, and uses a service account key file for a purpose other than the one for which the file was originally intended. For example:
Developer A creates SA 1
Developer A uses gcloud iam service-accounts keys create ... to create the secret file for SA 1, encrypts it, and checks it in to source control
Developer B creates SA 2
Developer B (mistakenly) decrypts and copies the secret file from step 2, modifies some of its fields with data from SA 2, then attempts to use it in an application
The resolution in this scenario obviously is for Developer B to get rid of the copied/edited file and create a new secret file with gcloud like Developer A did in step 2.
I had this same error occur when using a service account. I couldn't figure out what was wrong so I came back to it the next day and it worked. So maybe Google Cloud takes some time to propagate every once in a while.