I am able to read files in bucket in COS using IAM token but unable to do the same using API key.
I have set the following key in my request header:
Authorization: {api key}
Should I set anything else? Note I am not using HMAC credentials.
below code has two options one with bearer token and the other with api key created for that Cos instance specifically for the bucket.
example code:
var request = require('request');
//using bearer token
var options = {
uri: 'https://{endpoint}/{bucket name}',
headers: {
'Authorization': 'bearer {token string}',
}
};
//using api key
var options = {
uri: 'https://{endpoint}/{bucketname}',
headers: {
'Authorization': '{{api key string for cos service id}',
}
};
function callback(error, response, body) {
console.log(error)
console.log(response.statusCode)
if (!error && response.statusCode == 200) {
console.log(body)
}
}
request(options, callback);
Just use the ibm-cos-sdk npm library here https://www.npmjs.com/package/ibm-cos-sdk. You just have to do the following and you're on your way.
var AWS = require('ibm-cos-sdk');
var util = require('util');
var config = {
endpoint: '<endpoint>',
apiKeyId: '<api-key>',
ibmAuthEndpoint: 'https://iam.ng.bluemix.net/oidc/token',
serviceInstanceId: '<resource-instance-id>',
};
var cos = new AWS.S3(config);
API key should not be sent in the Authorization header at all. Its a key for one time generation of the token.
As A. J. Alger pointed out, I would suggest using some SDK (list provided below) to eliminate the redundant work.
Regrading the error you are getting while using the Node JS sdk, please provide more details on it.
https://www.npmjs.com/package/ibm-cos-sdk
https://github.com/IBM/ibm-cos-sdk-java
Related
I've been stuck on this issue for a while now, I'm using ADAL.js on the front-end to handle login and authentication. Once logged in I need to get the info for the user (roles, groups, name etc...) however I can't get anything back from the /adfs/userinfo endpoint other than a 401.
So far I log the user in and get back an id_token and access token (or "adal.access.token.key{guid}" in the browser) which is identical to the access key. Due to a cors issue on the front-end I then send this to a back-end mvc core 2.2 controller to make the call to /adfs/userinfo which is where I get the 401. Javascript code below
this.adalAuthentication.Instance.acquireToken(this.adalAuthentication.Instance.config.clientId, (error, token) => {
if (error || !token) {
console.log('ADAL Error Occurred: ' + error);
return;
}
axios({
method: 'get',
url: '/identity/completeLogin/' + token,
headers: {
'Authorization': 'Bearer ' + token
}
}).then((response) => { console.log(response.data) });
});
And controller action...
[HttpGet("completeLogin/{access_token}")]
public async Task<HttpResponseMessage> CompleteLogin(string access_token)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth" + access_token);
var response = await client.GetAsync("https://adfs.server/adfs/userinfo");
response.EnsureSuccessStatusCode();
try
{
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
catch (Exception e)
{
throw(e);
}
}
At this point I'm stumped, I'm thinking I either can't use ADAL for this or perhaps need to use oidcinstead of OAuth/jwt but I don't want to have to rewrite lots just to find out that doesn't work either or there's a better/best practice way of doing it. Has anyone had this issue before and/or can point me in the right direction or can see where I'm going wrong?
Other things I've tried;
hitting adfs/oauth/token endpoint (just returns with adfs server error)
setting Authorisation on the backend to client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer" + access_token); (just returns invalid token).
Making the front and backend a post method and using the getCachedToken method on the ADAL AuthenticationContext
EDIT: I also have this question open with a the slightly more specific goal of getting an access token with the id token
There's a Postman sample here.
Be aware that "userinfo" only returns a "sub" claim.
I have to put the API Key in the request header as:
Authorization: Bearer "YOUR API KEY"
This is my code (I'm not sure where to put the header and how)
Future<Stream<Book>> getBooks() async {
var url = ‘example_url’
var client = http.Client();
var streamedResponse = await client.send(
http.Request(‘get’, Uri.parse(url))
);
return streamedResponse.stream
.transform(utf.decoder)
.transform(json.decoder)
.expand(jsonBody) => (jsonBody as Map)[‘results’] )
.map((jsonBook) = Book.fromJson(jsonBook));
}
The Flutter docs https://flutter.io/cookbook/networking/authenticated-requests/ says to use this format for authenticated requests but this is not for streams, this returns a future of an object (Book)
Future<Book> fetchPost() async {
final response = await http.get(
'https://jsonplaceholder.typicode.com/posts/1',
headers: {HttpHeaders.authorizationHeader: "Place your_api_token_here"},
);
final responseJson = json.decode(response.body);
return Book.fromJson(responseJson);
}
You can add custom headers after you created the Request
final request = http.Request('GET'), url)
..headers.addAll(myHeaders);
I have made a custom header using http.Request as follow bellow :
final url =
'https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.6.0-amd64-xfce-CD-1.iso';
final request = Request('GET', Uri.parse(url));
request.headers.clear();
request.headers.addAll({"content-type":"application/json"});
I don't know if I'm just not looking in the right places, but I cannot seem to find the right guidance on where to begin working with React / .NET Core 2.1 Web API and (on-prem) Active Directory authentication.
I'm relatively new to .NET authentication in general, and completely new to Active Directory authentication.
I started by using the .NET Core 2.1 React template and attempting to add auth to it, but got completely lost.
Where do I even start?
For me, step one was to set up JWT authentication, such as described in this MSDN blog post.
Next, I had to find a library to use to check a user against Active Directory. I chose System.DirectoryServices.AccountManagement (available for .NET Core).
Now, I had to create a new controller with an [AllowAnonymous]attribute. I called it LoginController, and created an action that looked like the following:
[AllowAnonymous]
[HttpPost]
// Notice: We get a custom request object from the body
public async Task<IActionResult> Login([FromBody] AuthRequest request)
{
// Create a context that will allow you to connect to your Domain Controller
using (var adContext = new PrincipalContext(ContextType.Domain, "mydomain.com"))
{
var result = adContext.ValidateCredentials(request.username, request.password);
if (result)
{
// Create a list of claims that we will add to the token.
// This is how you can control authorization.
var claims = new[]
{
// Get the user's Name (this can be whatever claims you wish)
new Claim(ClaimTypes.Name, request.username)
};
// Read our custom key string into a a usable key object
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("SOME_TOKEN").Value));
// create some signing credentials using out key
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// create a JWT
var token = new JwtSecurityToken(
issuer: "mydomain.com",
audience: "mydomain.com",
claims: claims, // the claims listed above
expires: DateTime.Now.AddMinutes(30), // how long you wish the token to be active for
signingCredentials: creds);
Since we return an IActionResult, wrap the token inside of a status code 200 (OK)
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}
}
}
// if we haven't returned by now, something went wrong and the user is not authorized
return Unauthorized();
}
The AuthRequest object could look something like this:
public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}
Now, in my React app, all I have to do is make a simple fetch request to the LoginController with the user's username & password that I can get from a login form. The result will be a JWT I can save to state (But should save to cookies: the react-cookie library makes that trivial).
fetch(`login`, {
method: "POST",
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify({this.state.username, this.state.password})
}).then((response) => {
if (response.status === 401) {
// handle the 401 gracefully if this user is not authorized
}
else {
// we got a 200 and a valid token
response.json().then(({ token }) => {
// handle saving the token to state/a cookie
})
}
})
You now have the ability to add the [Authorize] attribute to any of your controllers in your .NET Core application, and make a fetch request to it while passing your JWT from your React client, like this:
await fetch(`someController/someAction`,
{
method: 'GET'
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${YOUR_JWT}`
}
})
.then(response => doSomething());
If you wanted to use this JWT with a SignalR Hub, add the [Authorize] attribute to your Hub in your .NET Core project. Then, In your React client, when you instantiate the connection to your hub:
import * as signalR from '#aspnet/signalr';
var connection = new signalR.HubConnectionBuilder().withUrl('myHub', { accessTokenFactory: () => YOUR_JWT })
And, viola! A .NET Core React application capable of authorized real-time communication!
I have a Lambda function that handles reading data from a file(stored inside S3 bucket) as well as inserting data to a Dynamodb table. This Lambda function is exposed as a REST endpoint using API gateway. The function accepts GET request as well as POST request. I'm making GET/POST requests from my REACT project using axios and aws4(for signing) libraries. GET request is to read data from a file stored inside S3 and it works just fine. And POST request is for inserting data into Dynamodb table. However, it doesn't work and AWS returns InvalidSignatureException error as a respond. This is an excerpt of my code :
createAWSSignedRequest(postData) {
let request = {};
if (postData) {
request = {
host: process.env.AWS_HOST,
method: 'POST',
url: process.env.AWS_URL,
path: process.env.AWS_PATH,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
}
} else {
request = {
host: process.env.AWS_HOST,
method: 'GET',
url: process.env.AWS_URL,
path: process.env.AWS_PATH
}
}
let signedRequest = aws4.sign(request, {
secretAccessKey: process.env.AWS_SECRET_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY
});
return signedRequest;
}
This is how GET request is made :
let signedRequest = this.createAWSSignedRequest('GET');
axios(signedRequest)
.then(response => {
})
.catch((error) => {
console.log("error",error);
});
This is how POST request is made :
const data = {
uuid: "916b7d90-0137-11e8-94e6-116965754e23", //just a mock value
date : "22/jan/2018",
user_response: [
{
question:"this is quesiton1",
choice:"user selected A"
},
{
question:"this is quesiton2",
choice: "user selected b"
},
{
question:"this is quesiton3",
choice: "user selected C"
}
]
};
let signedRequest = this.createAWSSignedRequest(data);
axios(signedRequest)
.then(response => {
......
})
.catch((error) => {
console.log("error",error);
});
As you can see, the code for both GET and POST requests are exactly the same (except payload and method type). I'm singing with the same secret access key and access key id for both requests. I'm not sure why one request results in "InvalidSignatureException" when the other doesn't. Can anyone shed a light on this issue for me.
Thanks
After having discussion with AWS4 lib developer, I figured out what I did wrong. AWS4 uses "body" as a payload attribute to compute signature. However, Axios uses "data" attribute as payload. My mistake was only setting either one of them. So when I set just "data" attribute, the payload was present in the request and content-length is computed correctly. However, the signature was incorrect since the payload was not taken into consideration when computing signature. When I set just "body", payload was not present in the request because Axios does not use "body" attribute for payload. The solution is to set both attributes with payload. I hope this helps to anyone who are having the same issue I have.
If you use the AWS Amplify library it has a module called API which should fit your use cases, and it will perform Sigv4 signing for you either with authenticated or unauthenticated roles. The Auth category uses Cognito as the default implementation. For instance:
npm install aws-amplify --save
Then import and configure the lib:
import Amplify, { API } from 'aws-amplify';
Amplify.configure({
Auth: {
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
region: 'XX-XXXX-X'
},
API: {
endpoints: [
{
name: "APIName",
endpoint: "https://invokeURI.amazonaws.com"
}
]
}
});
Then for your API Gateway endpoint calling a Lambda:
let apiName = 'MyApiName';
let path = '/path';
let options = {
headers: {...} // OPTIONAL
}
API.get(apiName, path, options).then(response => {
// Add your code here
});
More info here: https://github.com/aws/aws-amplify
Google Cloud Platform has introduced Identity Aware Proxy for protecting App Engine Flexible environment instances from public access.
However, it is not entirely clear if this can or should be used from Google Cloud Functions that are accessing GAE hosted API endpoints.
The documentation (with Python and Java examples) indicates an IAP authentication workflow consisting of 1) generating a JWT token, 2) creating an OpenID Token, 3) Then submitting requests to Google App Engine with an Authorization: Bearer TOKEN header.
This seems quite convoluted for running cloud functions if authorisation has to happen each time a function is called.
Is there another way for Google cloud functions to access secured GAE endpoints?
If you want to make calls from GCF to IAP protected app, you should indeed be using ID tokens. There are no examples in Nodejs so I made one using this as a reference (style may be wrong since that's the first time I touch nodejs). Unlike regular JWT claims set, it should not contain scope and have target_audience.
/**
* Make IAP request
*
*/
exports.CfToIAP = function CfToIAP (req, res) {
var crypto = require('crypto'),
request = require('request');
var token_URL = "https://www.googleapis.com/oauth2/v4/token";
// service account private key (copied from service_account.json)
var key = "-----BEGIN PRIVATE KEY-----\nMIIEvQexsQ1DBNe12345GRwAZM=\n-----END PRIVATE KEY-----\n";
// craft JWT
var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64');
// prepare claims set
var iss = "12345#12345.iam.gserviceaccount.com"; // service account email address (copied from service_account.json)
var aud = "https://www.googleapis.com/oauth2/v4/token";
var iat = Math.floor(new Date().getTime() / 1000);
var exp = iat + 120; // no need for a long linved token since it's not cached
var target_audience = "12345.apps.googleusercontent.com"; // this is the IAP client ID that can be obtained by clicking 3 dots -> Edit OAuth Client in IAP configuration page
var claims = {
iss: iss,
aud: aud,
iat: iat,
exp: exp,
target_audience: target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64');
// concatenate header and claimset
var unsignedJWT = [JWT_header, JWT_claimset].join('.');
// sign JWT
var JWT_signature = crypto.createSign('RSA-SHA256').update(unsignedJWT).sign(key, 'base64');
var signedJWT = [unsignedJWT, JWT_signature].join('.');
// get id_token and make IAP request
request.post({url:token_URL, form: {grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion:signedJWT}}, function(err,res,body){
var data = JSON.parse(body);
var bearer = ['Bearer', data.id_token].join(' ');
var options = {
url: 'https://1234.appspot.com/', // IAP protected GAE app
headers: {
'User-Agent': 'cf2IAP',
'Authorization': bearer
}
};
request(options, function (err, res, body) {
console.log('error:', err);
});
});
res.send('done');
};
/**
* package.json
*
*/
{
"name": "IAP-test",
"version": "0.0.1",
"dependencies": {
"request": ">=2.83"
}
}
Update: Bundling service account key is not recommended, so a better option is to use the metadata server. For the below sample to work Google Identity and Access Management (IAM) API should be enabled and App Engine default service account should have Service Account Actor role (default Editor is not enough):
/**
* Make request from CF to a GAE app behind IAP:
* 1) get access token from the metadata server.
* 2) prepare JWT and use IAM APIs projects.serviceAccounts.signBlob method to avoid bundling service account key.
* 3) 'exchange' JWT for ID token.
* 4) make request with ID token.
*
*/
exports.CfToIAP = function CfToIAP (req, res) {
// imports and constants
const request = require('request');
const user_agent = '<user_agent_to_identify_your_CF_call>';
const token_URL = "https://www.googleapis.com/oauth2/v4/token";
const project_id = '<project_ID_where_CF_is_deployed>';
const service_account = [project_id,
'#appspot.gserviceaccount.com'].join(''); // app default service account for CF project
const target_audience = '<IAP_client_ID>';
const IAP_GAE_app = '<IAP_protected_GAE_app_URL>';
// prepare request options and make metadata server access token request
var meta_req_opts = {
url: ['http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/',
service_account,
'/token'].join(''),
headers: {
'User-Agent': user_agent,
'Metadata-Flavor': 'Google'
}
};
request(meta_req_opts, function (err, res, body) {
// get access token from response
var meta_resp_data = JSON.parse(body);
var access_token = meta_resp_data.access_token;
// prepare JWT that is {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount for more info
var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64');
var iat = Math.floor(new Date().getTime() / 1000);
// prepare claims set and base64 encode it
var claims = {
iss: service_account,
aud: token_URL,
iat: iat,
exp: iat + 60, // no need for a long lived token since it's not cached
target_audience: target_audience
};
var JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64');
// concatenate JWT header and claims set and get signature usign IAM APIs projects.serviceAccounts.signBlob method
var to_sign = [JWT_header, JWT_claimset].join('.');
// sign JWT using IAM APIs projects.serviceAccounts.signBlob method
var signature_req_opts = {
url: ['https://iam.googleapis.com/v1/projects/',
project_id,
'/serviceAccounts/',
service_account,
':signBlob'].join(''),
method: "POST",
json: {
"bytesToSign": new Buffer(to_sign).toString('base64')
},
headers: {
'User-Agent': user_agent,
'Authorization': ['Bearer', access_token].join(' ')
}
};
request(signature_req_opts, function (err, res, body) {
// get signature from response and form JWT
var JWT_signature = body.signature;
var JWT = [JWT_header, JWT_claimset, JWT_signature].join('.');
// obtain ID token
request.post({url:token_URL, form: {grant_type:'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion:JWT}}, function(err, res, body){
// use ID token to make a request to the IAP protected GAE app
var ID_token_resp_data = JSON.parse(body);
var ID_token = ID_token_resp_data.id_token;
var IAP_req_opts = {
url: IAP_GAE_app,
headers: {
'User-Agent': user_agent,
'Authorization': ['Bearer', ID_token].join(' ')
}
};
request(IAP_req_opts, function (err, res, body) {
console.log('error:', err);
});
});
});
});
res.send('done');
};
For anyone still looking at this 2020 and beyond Google has made this very easy.
Their docs have an example of how to auth IAP that works great in Cloud Functions:
// const url = 'https://some.iap.url';
// const targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
async function request() {
console.info(`request IAP ${url} with target audience ${targetAudience}`);
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}
python example:
from google.auth.transport.requests import Request as google_request
from google.oauth2 import id_token
open_id_connect_token = id_token.fetch_id_token(google_request(), client_id)
where client_id is string. Navigate to APIs & Services part of GCP, then select credentails on the left, there is client_id in OAuth2.0 part in AIP
when yoy want to make request to secured IAP service, just add to headers
{'Authorization': 'Bearer your_open_id_connect_token'}
source: https://cloud.google.com/iap/docs/authentication-howto
As discussed in this doc, you can authenticate to a Google Cloud Platform (GCP) API using:
1- Service Accounts (preferred method) - use of a Google account that is associated with your GCP project, as opposed to a specific user.
2- User Accounts - used when app needs to access resources on behalf of an end user.
3- API keys - generally used when calling APIs that don’t need to access private data.
some people use the Google CLOUD KEY MANAGEMENT SERVICE (KMS) to avoid hardcoding them in the cloud function.
https://cloud.google.com/kms/