Hello I'm doing a REST API client with AngularJS using ngResource plugin and my implementation of HMAC authentication.
I wrote an HttpIntercept Service that intercepts the http requests and calculate and attach the Authorization Header with HMAC sign. But with this implementation it calculates and attaches the sign to all requests, that's bad.
.factory('authInterceptor', function($q) {
return {
request: function(request) {
#sign calculation...
request.headers['Authorization'] = sign;
}
return request || $q.when(request);
}
};
})
.controller('HomeCtrl', function ($scope,$resource) {
var Articles = $resource('/api/articles');
$scope.articles = Articles.query();
})
Do you have a suggestion to intercept only requests that needs authentication or all requests that came from ngResource plugin?
I thought to three workrounds:
1. an array list of the private requests
2. different subdomain for public and private APIs
3. attach supply http Header to the requests that need authentication
See $http and overriding transformations and also $resource
Each $resource action takes an $http.config like object which has transformRequest:
var Articles = $resource(
'/api/articles',
{
},
{
'query': {
method: 'GET',
isArray: true,
transformRequest: function (config) {
config.headers['Authentication']: 'sign';
return config;
}
}
});
Related
Trying to allow a CLI I'm developing to "login" via web browser and obtain an access token for the user's account, similar to how gcloud and github's CLIs do it. I realize it'll be using the OAuth Authorization Code flow.
But what about the client_secret?
I've found out that github cli just doesn't care about exposing it, and it's right there in the source code: https://github.com/cli/cli/blob/6a8deb1f5a9f2aa0ace2eb154523f3b9f23a05ae/internal/authflow/flow.go#L25-L26
Why is this not a problem? or is it?
I'm not yet using OAuth for the cli's login
STANDARDS
The CLI app is a native public client and should use authorization code flow + PKCE rather than a fixed client secret. It should also follow the flow from
RFC8252 and receive the browser response using a local HTTP (loopback) URI.
THIS IMPLEMENTATION
Looks like the github code here uses a client secret and does not use PKCE. You may have to provide a client secret if using this library, but it cannot be kept secret from users. Any user could easily view it, eg with an HTTP proxy tool.
CODE
If the infrastructure enables you to follow the standards, aim for something similar to this Node.js code.
* The OAuth flow for a console app
*/
export async function login(): Promise<string> {
// Set up the authorization request
const codeVerifier = generateRandomString();
const codeChallenge = generateHash(codeVerifier);
const state = generateRandomString();
const authorizationUrl = buildAuthorizationUrl(state, codeChallenge);
return new Promise<string>((resolve, reject) => {
let server: Http.Server | null = null;
const callback = async (request: Http.IncomingMessage, response: Http.ServerResponse) => {
if (server != null) {
// Complete the incoming HTTP request when a login response is received
response.write('Login completed for the console client ...');
response.end();
server.close();
server = null;
try {
// Swap the code for tokens
const accessToken = await redeemCodeForAccessToken(request.url!, state, codeVerifier);
resolve(accessToken);
} catch (e: any) {
reject(e);
}
}
}
// Start an HTTP server and listen for the authorization response on a loopback URL, according to RFC8252
server = Http.createServer(callback);
server.listen(loopbackPort);
// Open the system browser to begin authentication
Opener(authorizationUrl);
});
}
/*
* Build a code flow URL for a native console app
*/
function buildAuthorizationUrl(state: string, codeChallenge: string): string {
let url = authorizationEndpoint;
url += `?client_id=${encodeURIComponent(clientId)}`;
url += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
url += '&response_type=code';
url += `&scope=${scope}`;
url += `&state=${encodeURIComponent(state)}`;
url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
url += '&code_challenge_method=S256';
return url;
}
/*
* Swap the code for tokens using PKCE and return the access token
*/
async function redeemCodeForAccessToken(responseUrl: string, requestState: string, codeVerifier: string): Promise<string> {
const [code, responseState] = getLoginResult(responseUrl);
if (responseState !== requestState) {
throw new Error('An invalid authorization response state was received');
}
let body = 'grant_type=authorization_code';
body += `&client_id=${encodeURIComponent(clientId)}`;
body += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
body += `&code=${encodeURIComponent(code)}`;
body += `&code_verifier=${encodeURIComponent(codeVerifier)}`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body,
};
const response = await fetch(tokenEndpoint, options);
if (response.status >= 400) {
const details = await response.text();
throw new Error(`Problem encountered redeeming the code for tokens: ${response.status}, ${details}`);
}
const data = await response.json();
return data.access_token;
}
I'm doing a project with vue, nuxt and keycloak as server for token, axios as http client and #nuxtjs/auth-next module for keycloak access.
I'm using a public client so I don't have a secret key which is the most recommended.
The part of getting the token and talking to the backend is working.
But as it is a public client it has no refresh token.
Searching the internet, a recommendation would be to post from time to time to the keycloak /token endpoint, passing the current token, to fetch a new token.
To perform this post, it doesn't work to pass json, having to pass application/x-www-form-urlencoded.
But it generates an error saying that the parameter was not passed.
On the internet they recommended passing it as url string, but then it generates an error on the keycloak server, as a parameter that is too long, because of the current token that is passed.
Below is the code used to try to fetch a new token.
This code is being called on a test-only button.
If anyone can help, I appreciate it.
const token = this.$auth.strategy.token.get()
const header = {
"Content-Type": "application/x-www-form-urlencoded"
}
const body = {
grant_type: "authorization_code",
client_id: "projeto-ui",
code: token
}
this.$axios ( {
url: process.env.tokenUrl,
method: 'post',
data: body,
headers: header
} )
.then( (res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
} );
Good afternoon people.
Below is the solution to the problem:
On the keycloak server:
it was necessary to put false the part of the implicit flow.
it was necessary to add web-origins: http://localhost:3000, to allow CORS origins.
In nuxt.config.js it was necessary to modify the configuration, as below:
auth: {
strategies: {
keycloak: {
scheme: 'oauth2',
...
responseType: 'code',
grantType: 'authorization_code',
codeChallengeMethod: 'S256'
}
}
}
I have a mocha javascript file in which I have require function to login to the application in headless browser mode, login using the crendentials and return the jwt authentication.
I want to call this script through K6. But as I understand, calling node module java script from K6 is not possible?
Is there an alternative to this?
I have also just started implementing k6 and had same step to do ;)
Here is how I have done it.
you need to know how to authenticate to the API you want to use. I assume we have it, as you wrote you want to use node modules.
second, use appropriate method to communicate with API
next, catch token and append it to next requests headers
finally, test API with requests you want
I found code snippets on web page with k6 samples for APIs.
I have shorten a bit, sample code and end up with:
import {
describe
} from 'https://jslib.k6.io/functional/0.0.3/index.js';
import {
Httpx,
Request,
Get,
Post
} from 'https://jslib.k6.io/httpx/0.0.2/index.js';
import {
randomIntBetween,
randomItem
} from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
export let options = {
thresholds: {
checks: [{
threshold: 'rate == 1.00',
abortOnFail: true
}],
},
vus: 2,
iterations: 2
};
//defining auth credentials
const CLIENT_ID = 'CLIENT_ID';
const CLIENT_SECRET = 'CLIENT_SECRET';
let session = new Httpx({
baseURL: 'https://url.to.api.com'
});
export default function testSuite() {
describe(`01. Authenticate the client for next operations`, (t) => {
let resp = session.post(`/path/to/auth/method`, {
//this sections relays on your api requirements, in short what is mandatory to be authenticated
grant_type: GRANT_TYPE,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
});
//printing out response body/status/access_token - for debug
// console.log(resp.body);
// console.log(resp.status);
// console.log(resp.json('access_token'));
//defining checks
t.expect(resp.status).as("Auth status").toBeBetween(200, 204)
.and(resp).toHaveValidJson()
.and(resp.json('access_token')).as("Auth token").toBeTruthy();
let authToken = resp.json('access_token');
// set the authorization header on the session for the subsequent requests.
session.addHeader('Authorization', `Bearer ${authToken}`);
})
describe('02. use other API method, but with authentication token in header ', (t) => {
let response = session.post(`/path/to/some/other/post/method`, {
"Cache-Control": "no-cache",
"SomeRequieredAttribute":"AttributeValue"
});
t.expect(response.status).as("response status").toBeBetween(200, 204)
.and(response).toHaveValidJson();
})
}
I could use some guidens, sending an object from my angular 2 application to the Web API.
I know how to GET objects from the Web Api, to my angular 2 application, but can't seem to figure out how the post method works or even if I should use the http.post methodd.
My angular 2 application has the following method:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
this.http.post('http://localhost:52262/api/postbookings', objectToSend)
.map((res: Response) => res.json()).subscribe(res => result = res);
console.log(result);
}
The "updatedReservation" is an object, which I convert to JSON.
The Web api can be reached by the following address:
httl://localhost:52262/api/postbookings
Web Api controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PostBookingsController : ApiController
{
[AcceptVerbs()]
public bool ConfirmBooking(Booking booking)
{
return true;
}
}
What I'm trying to do is to send the object, update my database based on the changes values that the object has. Then send back true or false if this is a confirmation or not so I can redirect to confirmation page.
Do any know the unsupported media type error?, is that related to that the object i send is not what the api method expects?
Hope someone can help.
You need to set the Content-Type header when sending the request:
sendUpdatdReservation(updatedReservation: Reservation) {
var result;
var objectToSend = JSON.stringify(updatedReservation);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post('http://localhost:52262/api/postbookings', objectToSend, { headers: headers })
.map((res: Response) => res.json()).subscribe(res => {
this.result = res;
console.log(this.result);
});
}
Don't forget to import this class:
import {Http,Headers} from 'angular2/http';
I'm trying to setup a simple Ember.js app to talk with a custom API server, with JWT authentication.
I can login at the API server and obtain a JWT token, but then no Authorization header is set in subsequent calls to the API server.
My login controller is:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'simple-auth-authenticator:jwt';
this.get('session').authenticate(authenticator, credentials).then(function() {
// authentication was successful
console.log('OK');
}, function(err) {
// authentication failed
console.log('FAIL ' + JSON.stringify(err));
});
},
logOut: function() {
this.get('session').invalidate();
}
}
});
I can successfully login and obtain a token. My login route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
sessionAuthenticationFailed: function(error) {
console.log('Login error: ' + error.ErrorDesc);
this.controllerFor('login').set('loginErrorMessage', error.ErrorDesc);
this.controllerFor('login').set('ErrorMoreInfo', error.MoreInfo);
},
sessionAuthenticationSucceeded: function() {
console.log('Session authenticated: ' + this.get('session').content.secure.token);
// redirect to last route requested, or to default route
var attemptedTransition = this.get('session').get('attemptedTransition');
if (attemptedTransition) {
attemptedTransition.retry();
this.get('session').set('attemptedTransition', null);
} else {
this.transitionTo('index');
}
}
}
});
...shows me the token is properly acquired, and correctly redirects me to my protected routes (e.g. index). Since then, if I try to get any data from the API server, it does not receive any "Authorization: Bearer [token]" header at all.
My environment configuration:
ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:token'
};
ENV['simple-auth-token'] = {
refreshAccessTokens: true,
timeFactor: 1000,
refreshLeeway: 300, // Refresh the token 5 minutes (300s) before it expires.
serverTokenEndpoint: 'https://localhost:8000/login',
crossOriginWhitelist:[
'http://localhost:4200',
'https://localhost:8000'
],
identificationField: 'user',
passwordField: 'password',
tokenPropertyName: 'token',
authorizationPrefix: 'Bearer ',
authorizationHeaderName: 'Authorization',
// headers: {},
};
I also tried manually setting the header by calling jqXHR.setRequestHeader overriding the authorize function in my login route, but with no success:
authorize: function(jqXHR, requestOptions) {
var auth= "Bearer " + this.get('session').content.secure.Token;
console.log('Add authorization header ' + auth);
console.log( JSON.stringify(requestOptions));
jqXHR.setRequestHeader("Authorization", auth);
}
Can anybody tell what I'm missing? Shouldn't simple-auth-token take care of adding the header automatically?
Thanks for any help,
al.
I had the same issue, with a REST adapter making calls on a different port.
Solved adding
ENV['simple-auth'] = {
crossOriginWhitelist: ['*']
}
Xabi's answer is working for me. But I didn't find it intuitive.
"Authorized requests" comply to a restrictive CORS policy : the authorization is not added in case of CORS issue.
In the docs :
Ember Simple Auth will never authorize requests going to a different origin than the one the Ember.js application was loaded from.
But requests that don't need an authorizer (with no 'Authorization' header in case of JWT) are allowed and working fine.