I've setup a perfectly functioning application (in VB) that allows user to access his Google Sheets.
The application follows Google's OAuth documentation for displaying a sign-in dialog in a web browser, obtains user's permission and access codes, uses access codes to obtain access token, and then uses the Google Sheet's Query service to get hold of Google Sheets. Very simple. Works fine.
Problem occurs on computers that have internet proxy defined on them. In the rest of my application and most of Google Sheets API, I can define a manual internet proxy. GData's RequestFactory allows manually configuring proxy server. The only line of code that doesn't support (to my current knowledge) is the OAuthUtil library used for obtaining access token. It doesn't allow defining internet proxy server, hence it is unable to resolve host on computers behind proxy environment. Following is my pseudo code:
Dim parameters As New OAuth2Parameters
parameters.ClientId = CLIENT_ID
parameters.ClientSecret = CLIENT_SECRET
parameters.RedirectUri = REDIRECT_URI
parameters.Scope = SCOPE
>>Show browser window and obtain access code
parameters.AccessCode = login.Token
OAuthUtil.GetAccessToken(parameters) '<< Point of failure
Dim requestFactory As GOAuth2RequestFactory = New GOAuth2RequestFactory(Nothing, My.Application.Info.ProductName, parameters)
requestFactory.Proxy = GetProxySettings() '<< my code for defining proxy
myService = New SpreadsheetsService("Application")
myService.RequestFactory = requestFactory
Another important aspect is that my application works on Mac OSX as well using Wine (for web browser I use GeckoFX). If internet proxy is globally defined on the environment then the OAuthUtil works fine, but this doesn't work for Wine. I have tried setting internet proxy in the command-line environment, or in the registry and refreshed system settings, still the applications running in Wine do not understand that proxy is defined. Hence proxy has to be manually defined.
I need help to figure out a solution by any of the following:
* A way to forcefully/manually define proxy for OAuthUtil for obtaining access token
* Any other way to obtain OAuth access token if proxy cannot be defined as above (maybe WebClient can be used?)
* Some way to define global internet proxy in Wine so applications like GData API read and understand the proxy setting. Though I'd rather prefer manually defined proxy at application level.
Any ideas folks?
Regards
F.A.
I've figured it out. Turns out that the 'OAuthUtil.GetAccessToken' only uses system-defined proxy. There is no way to manually define internet proxy, like RequestFactory supports. So there is a work-around using WebClient:
Try
'// Get access token from code
Using WC As New WebClient
' Define proxy
WC.Proxy = GetProxySettings()
' Set parameters
WC.Headers(HttpRequestHeader.ContentType) = "application/x-www-form-urlencoded"
' Get response
Dim postURL = "https://www.googleapis.com/oauth2/v4/token"
Dim postParams = "code=" & parameters.AccessCode &
"&client_id=" & Uri.EscapeDataString(CLIENT_ID) &
"&client_secret=" & Uri.EscapeDataString(CLIENT_SECRET) &
"&redirect_uri=" & Uri.EscapeDataString(REDIRECT_URI) &
"&grant_type=authorization_code"
Dim responsebody As String = WC.UploadString(postURL, postParams)
' Read response
Dim jObj As JObject = JsonConvert.DeserializeObject(responsebody)
' Store token
parameters.AccessToken = jObj("access_token").ToString
parameters.RefreshToken = jObj("refresh_token").ToString
parameters.TokenType = jObj("token_type").ToString
parameters.TokenExpiry = Now().AddSeconds(CDbl(jObj("expires_in").ToString))
End Using
Catch ex As Exception
MsgBox("Error obtaining access token: " & ex.Message, MsgBoxStyle.Critical)
Return Nothing
End Try
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?
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.
JIRA provides a way to access the attachments of an issue using basic auth, jwt auth mechanisms. Using which we can download those files. We're able to download the files using both authentication mechanisms.
sample jwt auth:
curl -X GET --url https://{site-name}.atlassian.net/secure/attachment/1001/example.txt -H 'Authorization: jwt '
Issue / Our requirement:
But is there a way to generate temporarily accessible url for the JIRA issue's attachments which will have token embedded into the URI itself. I've added the example of that below
example url:
https://{site-name}.atlassian.net/attachment/1001/example.txt?token={temp_access_token}
While accessing / clicking on the above mentioned url the download should automatically start even if the user isn't logged into their account
Reason for our requirement:
We're creating jira cloud based service / app and one of its feature is providing access to the user's attachments through our application. Our limitation(cloud service cost) is that we can't download all the huge sized attachments and store and manage it. So we're looking for a solution using which user's can download from the JIRA's server directly
In your JWT generation steps, you can define how long the JWT should be valid. And you can attach a JWT to the URL like this: <Jira Base Url>/rest/api/3/...?jwt=.... This way, you could generate a JWT on demand and it'll only be valid for the given time that you define.
In the Java Example on the page Understanding JWT for Connect apps you can see how they are setting the expirationTime. Just do the same, on demand. Here is the important part of the code snippet:
public class JWTSample {
public String createUriWithJwt()
throws UnsupportedEncodingException, NoSuchAlgorithmException {
long issuedAt = System.currentTimeMillis() / 1000L;
long expiresAt = issuedAt + 180L;
/* ... */
JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()
.issuedAt(issuedAt)
.expirationTime(expiresAt)
.issuer(key);
/* ... */
String jwtToken = /* ... */;
String apiUrl = baseUrl + apiPath + "?jwt=" + jwtToken;
return apiUrl;
}
}
Security concern: I'm explicitly mentioning that you should generate these links on demand because you should not set the expiration date to more than 5-10 minutes (which is already quite high). Otherwise an attacker just needs to retrieve your generated link (URLs are often logged somewhere) and is also able to retrieve the attachment as well.
Alternative Approach
Since you mentioned you'll build a service/app, why not chain the attachment download through your service? This way you wouldn't have to expose a JWT which is a potential security threat. For example: you offer a download button in your UI, this sends a HTTP request to your service and your service downloads the attachment and then forwards it to your user. However, this would not comply with your requirement to give access to unauthenticated users - if that's really what you want to do.
This one has me for a while now, I am trying to build a console app that can call a .net web/wcf service SP, the first leg is to get a token from the idP (ADFS4.0) the pasted code was working fine for a whole day, at some point it stopped working with the following error:
SOAP security negotiation with 'https://adfs.domain.in/adfs/services/trust/13/windowsmixed' for target 'https://adfs.domain.in/adfs/services/trust/13/windowsmixed' failed. See inner exception for more details.
The inner error is:
The Security Support Provider Interface (SSPI) negotiation failed.
NativeErrorCode: 0x80090350 -> SEC_E_DOWNGRADE_DETECTED
I have tried /13/windows and /windowstransport as well as the endpoint.
private static GenericXmlSecurityToken RequestSecurityToken()
{
// set up the ws-trust channel factory
var factory = new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(new WindowsWSTrustBinding(
SecurityMode.TransportWithMessageCredential), new EndpointAddress(new Uri("https://adfs.domain.in/adfs/services/trust/13/windowsmixed"), EndpointIdentity.CreateSpnIdentity("adfs#domain.in")));
factory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new System.ServiceModel.EndpointAddress(endpoint_address)
};
// request token and return
return factory.CreateChannel().Issue(rst) as GenericXmlSecurityToken;
}
In my case, for some reason, the ADFS was available over VPN but the AD based authentication bits are not happening over VPN. That's why SEC_E_DOWNGRADE_DETECTED is coming. In a regular non VPN environment things are good.
Also, another observation is once SAML token is generated over a regular enterprise network. Subsequent calls to generate the SAML token are going through as expected even on VPN.
So, if you see this error just check if the network you are in is part of the domain (and not public or private network), for SSPI negotiation.
Youtube is not accessible in my region, I use a VPN (cyberghost VPN) and use it to hit youtube APIs, to be specific this is how im requesting (through .NET client library):
below is my sample code
YouTubeRequestSettings settings = new YouTubeRequestSettings(Config.YoutubeApplicationName, Config.YouTubeDeveloperKey, Config.YouTubeConnectionUserName, Config.YouTubeConnectionPassword);
YouTubeRequest request = new YouTubeRequest(settings);
Video newVideo = new Video();
newVideo.Title = "XXXXXXXXXXXXXX";
newVideo.Tags.Add(new MediaCategory("XXXXXXXXXXX", YouTubeNameTable.CategorySchema));
newVideo.Keywords = "xxxxx";
newVideo.Description = "XXXXXXXXXXXXXXXXXXXXXXXX";
newVideo.YouTubeEntry.Private = true;
FormUploadToken token = request.CreateFormUploadToken(newVideo);
i get an email about "suspicious login attempt" which makes sense...since Im now logging in through a different country, but I thought I would be able to whitelist some IPs and get through...CANT find any such option there!!
question 1:
Is there an option in youtube developer account to whitelist an IP (a set of IPs)?
Question 2:
If its not possible, HOW else do I do it ? (one possible solution seems to be generation of "Application specific password" )?
I'd recommend switching to OAuth 2 instead of ClientLogin. Using OAuth 2, you authenticate once in a browser using your Google Account, authorize access, and then you get back a token that can be reused to make API calls without having to login again.
https://developers.google.com/youtube/2.0/developers_guide_protocol_oauth2