Background
I'm using these technologies to secure a WebApi application:
ThinkTecture.IdentityServer3
OWIN (Azure)
Single page app - javascript client
Refer to the Simple OAuth2 Walkthrough sample (github)
In the sample above the .NET console app client requests a token from IdentityServer and uses it to access the WebApi application. This works fine in the sample.
I want to change the .NET console app Client to a javascript single page app Client. I have tried adding a proxy login controller that does the request to IdentityServer on behalf of the javsacript Client and returns the token back to the client in a cookie.
Code
[HttpPost]
[Route("login")]
public HttpResponseMessage Login(LoginRequest request) // Proxy to IdentityServer3
{
var tokenClient = new TokenClient(
"https://localhost:44333/connect/token",
"javascript client",
"client secret");
var tokenResponse = _tokenClient.RequestResourceOwnerPasswordAsync(request.username, request.password, "api1").Result;
var cookie = new CookieHeaderValue("access_token", tokenResponse.AccessToken);
cookie.Expires = DateTimeOffset.Now.AddDays(1);
cookie.Domain = "localhost";
cookie.Path = "/";
var response = new HttpResponseMessage();
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
}
I get the access token back successfully in the javascript client, however the API doesn't recognise it.
Question
How should I pass the a token generated by IdentityServer to the javascript application, and how do I use it to access the WebApi?
Let's say your token end point is "https://localhost:44333/connect/token" (as you have mentioned). Hitting that endpoint with a POST request with a body like the following will return a token:
grant_type=password&client_id=youtclientid&client_secret=yourclientsecret&username=yourUserName&password=YourPassword&scope=list_of_requested_scopes
You use a JS variable to store your token, and then in order to use that token to access protected APIs, you have to send it as part of the header in the request, similar to the following: In your request's header, you will have:
Authorization: Bearer eyJ0eXAiOiJKV...
where "eyJ0eXAiOiJKV..." is the token you received in the first step.
Using oidc-client.js or creating something similar on those lines as the size of this js library is large.
Check this link out.
https://github.com/IdentityModel/oidc-client-js/tree/master/sample
and the video
https://vimeo.com/131636653
We are using the IdentityServer infrastructure to get to initial login and then the token(s) id/access for all further communications.
Related
I have a JS client side application that sends a POST request to an Azure Endpoint, using hardcoded SPA client_id and most importantly client_secret as displayed below:
function(request, sender, sendResponse) {
let data = new FormData();
data.append('client_id', "xxx");
data.append("client_secret", "xxxx");
data.append("grant_type", "password");
data.append("scope", "https://management.azure.com/user_impersonation");
data.append("username", request.usr);
data.append("password", request.pass);
const myHeaders = new Headers({
Accept:"application/json"
});
let resp = fetch(url, {
headers : myHeaders,
method: "POST",
body: data,
mode: 'cors'
}).then( (response) => {
// do some stuff
})
}
I need to protect these secrets as they should never be accessed by the client directly, so i have created a backend node server in Azure, that listens for the JS client.
But i'm confronted to the same problem again :
How to authenticate the JS client app so that only it, can post requests to the node Azure service, without storing a secret / certificate in the client side ?
As of now the node service is only accessible through authenticated accounts, but if i have to load a token in the JS client again, i go back to the original problem ...
Is there a different way to look at this problem ? Or an other techique to achieve this sort of flow ?
The standard way to retrieve access tokens to call backend services from a client side app is to use an authorization code flow. Usually you'll use a library for this, such as MSAL.
Using authorization code flow, the user will go through the Oauth login process, and you'll write code to request an access token on the user's behalf using their security context.
The user will have to have access to the resources in question, and in AAD you'll have to enable the API permissions for management.azure.com.
When locally trying to use Google books api to get public data:
Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
API_URL = "https://www.googleapis.com/books/v1/volumes";
this.http
.get<GoogleBooksApiInterface>(`${this.API_URL}?q=${title}`)
Even after adding &key=api_key, the response is the same.
After spent some time to figure it out and found solution (which is not unfortunately mentioned in google documentation):
to make it works in my case I use NodeJS on the backend side and moved there this API request.
Angular => NodeJS => API call => Angular
I am making a chat program.
I am using an Nginx server and NodeJS.
I have setup a websocket via ssl and that works fine.
I have decided to use cookies for authentication.
There are two functions which are crucial:
mconnection.prototype.make_server_https=function(){
console.log('Make server https');
var cthis=this;
var server_https=modules.https.createServer({
key: this.ssl_key,
cert:this.ssl_cert,
ca:this.ssl_ca
},(request,response)=>{
console.log('### CreateServer ###');
console.log('CreateServer, Request:');
console.log(request);
console.log('CreateServer, Response:');
console.log(response);
console.log('######');
and
mconnection.prototype.make_server_websocket=function(){
var server_websocket=new modules.ws.Server({server:this.server_https});
var cookie = require("cookie");
var cthis=this;
//whenever a new client connects with the server.
server_websocket.on('connection', function(client_socket, request){
console.log('### On Connection ###');
console.log('OnConnection, Client Socket:');
console.log(client_socket);
console.log('OnConnection, Request:');
console.log(request);
console.log('######');
If I do state the port number in the client url,function make_server_https gets run and inside there i can access the cookie and set it via the response object.
but in the original url,function make_server_websocket gets run, and there i have access to the client_socket on the server. But there it seems i dont have access to the cookies.
I need to client_websocket to start the connection with this given client. And I need to tie it somehow with the cookies login information.
But i never have both at the same time so i dont get how i could connect them to make the login happen.
I am probably misunderstanding something, any help in the right direction would really be appreciated.
you have to serve you index page from node server using GET then when the request reaches backend you will have response object which can then be used to SET-COOKIE if not set from backend.
And after GET request is complete COOKIE will be added in browser, when next request is made for websocket connection COOKIE will be added to the request in REQUEST HEADERS by the browser which will be available in backend through request object.
And if you decide to use it in login system then you can SET-COOKIE on successfull login.
i got it. its an event called on headers, not on connection. and there i can just push onto the headers.
I need a javascript library to connect to my web-socket server which is implemented using python twisted. I tried Native javascript web-socket client but it doesn’t have the option to pass custom headers as per this link. My web-socket server does authentication by taking auth_token from handshake header as in Oauth2 standard. Is there any javascript library available for web-socket clients which allows to pass custom header while connecting ?
I'm sorry to be the bearer of bad news... but - as mentioned both in the question you are referencing and as you can learn from the standard Websocket API (this isn't an external library, it's what comes with the browser)... you cannot set custom headers for websocket connections.
The WebSocket(url, protocols) constructor takes one or two arguments. The first argument, url, specifies the URL to which to connect. The second, protocols, if present, is either a string or an array of strings. ... Each string in the array is a subprotocol name. The connection will only be established if the server reports that it has selected one of these subprotocols. ...
But, all is not lost.
Since this is YOUR websocket server, you have options:
I'm pretty sure that OAuth2 uses the token as a parameter for a GET or POST request and NOT as a custom header. This means that (maybe) you can pass the token as part of the connection string, i.e.:
websocket = new WebSocket('wss://my.server.com/?access_token=secret_acess_token');
Passing the session token like so might not be ideal and could pose a security risk... so I would go with the second options here:
New websocket connections (unless my browsers are special) are initiated with the same cookies that the main connection was established with - this means that all the cookies and session data from the Http layer is accessible to the websocket layer....
So, It's possible to set a unique cookie - or, even better (assuming your http and websocket share the same codebase and work well together), set an authentication token within a server-side session storage - and use that data to authenticate a connection or to refuse it.
Since I'm no Python expert, here's a quick demo using Ruby's Plezi framework (I'm the author):
require 'plezi'
class DemoCtrl
# this is the Http index page response
def index
response.write "#{cookies[:notice]}\n\n" if cookies[:notice] && (cookies[:notice] = nil).nil?
#returning a string automatically appends it to the response.
"We have cookies where we can place data:\n#{request.cookies.to_s}\n"
end
# the login page
def login
cookies[:my_token] = "a secret token"
cookies[:notice] = "logged in"
redirect_to :index
end
# the logout page
def logout
cookies[:my_token] = nil
cookies[:notice] = "logged out"
redirect_to :index
end
# a Plezi callback, called before a websocket connection is accepted.
# it's great place for authentication.
def pre_connect
puts "Websocket connections gave us cookies where we can place data:\n#{request.cookies.to_s}\n"
return false unless cookies.to_s[:my_token] == "a secret token"
# returning true allows the connection to be established
true
end
def on_message data
puts "echoing #{data}"
response << "echo: #{data}"
end
end
# setup the route to our demo
Plezi.route '/', DemoCtrl
# Plezi will start once the script is finished.
# if you are running this in irb, use:
exit
visit: http://loaclhost:3000/
to try and initiate a websocket, open up the web inspector and run the following script in the console:
ws = new WebSocket("ws://localhost:3000/"); ws.onopen = function(e) { console.log("open"); }; ws.onmessage = function(e) { console.log(e.data);};
ws.send("Go Bears");
This should FAIL, because we didn't authenticate yet...
visit http://loaclhost:3000/login and try again.
Now it should work.
Try http://loaclhost:3000/logout if you feel like it.
I have the following situation: I have two applications that I need to make talk to each other: a JSF login page and an angular-js application. I cannot modify the JSF login page. I can modify the angular application.
A user logs in by accessing the JSF login page. A user access the JSF page from a browser and fills the log in form. If successfully authenticated, JSF will produce a token and send that token in a header with a redirect to the index.html that has the angular-js application. How can the angular-js application grab that token from that redirect?
It depends on from what kind of request do you need headers.
In case of xhr, most probably you will do your requests with http service. Its promise gets (in both states: failed and succeed) an object as an argument with a header() method, the one you need. You can get more information here:
The response object has these properties:
data - {string|Object} – The response body transformed with the transform functions.
status - {number} – HTTP status code of the response.
headers - {function([headerName])} – Header getter function.
config - {Object} – The configuration object that was used to generate the request.
statusText {string} – HTTP status text of the response.
EDIT
There are difficulties when you need to get headers not from an xhr but from the initial page request, before angular is bootstrapped. Unfortunately javascript can not access them.
One solution could be using cookies instead of header to store the data you need. But in this case you should do some changes on the request emitter side.
The other way, could be handling requests on the server, before html is being provided. Afterwards, you can pass them directly in markup, or remember them somehow, and send an xhr request for them directly from angular. But if you have a simple static page, you won`t be able to achieve it.
I think you have to create a http interceptor that will get the header from the http request, than store it in a cookie or in the local storage.
EDIT:
Try this:
module.factory('myTokenInterceptor', ['$cookies', function($cookies) {
var myTokenInterceptor = {
'request': function(config) {
if (config.url=='index.html')
{
var myToken=config.headers['myToken'];
$cookies.put('myToken', myToken);
};
}
};
return myTokenInterceptor;
}]);
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('myTokenInterceptor');
}]);
"module" can be anyone of your angular app modules.
In your controller, you can get the value of the token by using:
var token=$cookies.get('myToken');
I do have similar scenario.
Authenticating user by JSF Application login page (App1), and on link click invoking servlet, inside doPost() creating Cookie with token/JWT and adding cookie into responds header and redirecting to Angular JS ( App 2).
Angular app reading token from cookie and working well in local environment.
but the same not working with real domain name. getting cookie undefined.
Inside doPost() Domain.com/App1
Cookie cookie = new Cookie("AUTH_SESSION", URLEncoder.encode(
token, "UTF-8"));
cookie.setDomain(domain.com);
cookie.setMaxAge(30);
cookie.setHttpOnly(false);
cookie.setPath(“/”);
response.addCookie(cookie);
String redirectUrl = bean.getRedirectServletUrl();
response.sendRedirect(redirectUrl);
Inside AngularApp
Domain.com/app2
angular.module('app').service('CookieInitializer',
['$cookies', 'Session', '$location', CookieInitializer]);
function CookieStateInitializer($cookies, Session, $location) {
var init = function() {
var cookie = $cookies.get('FS_SESSION'); // undefined when using domain name
both application are differentiated by context path. and proxy added to make both application working with same domain name.
Domain.com/app1
domain.com/app2 --> apphost.com:443