CherryPy and CORS - javascript

I have an nginx server set up where I would like to set up a service that receives a string and returns a result. I plan to use Python to do the processing, with CherryPy as an interface. I've tested the CherryPy part, and know it receives properly. When I try to connect to the CherryPy service with a web page, I get CORS errors. How can I get them to communicate?
Here's the Python Code:
import cherrypy
import random
import urllib
class DataView(object):
exposed = True
#cherrypy.tools.accept(media='application/json')
def GET(self):
rawData = cherrypy.request.body.read(int(cherrypy.request.headers['Content-Length']))
b = json.loads(rawData)
return json.dumps({'x': 4, 'c': b})
def CORS():
cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
if __name__ == '__main__':
conf = {
'/': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.CORS.on': True,
}
}
cherrypy.tools.CORS = cherrypy.Tool('before_handler', CORS)
cherrypy.config.update({'server.socket_port': 3000})
cherrypy.quickstart(DataView(), '', conf)
Here's my web page:
<html lang="en">
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<link href="http://code.jquery.com/ui/1.10.4/themes/ui-lightness/jquery-ui.css" rel="stylesheet">
<script type="text/javascript">
$(document).on('click', "#submitButton", function(){
$.ajax({
type: 'GET',
url: 'http://localhost:3000',
contentType: 'text/plain',
xhrFields: {
// The 'xhrFields' property sets additional fields on the XMLHttpRequest.
// This can be used to set the 'withCredentials' property.
// Set the value to 'true' if you'd like to pass cookies to the server.
// If this is enabled, your server must respond with the header
// 'Access-Control-Allow-Credentials: true'.
withCredentials: false
},
headers: {
},
success: function() {
console.log("Success");
},
error: function() {
console.log("Fail");
}
});
});
</script>
</head>
<body>
<div id="header">
<h2>PDE Grammar Engine</h2>
<form>
Input Sentence:<br>
<input type="text" name="query" id="query"><br>
<input type="submit" id="submitButton" value="Submit">
</form>
</div>
</div id="results">
</div>
</body>
</html>

Turned out that the CherryPy server was not actually listening to the correct address. It was allowing connections from localhost, but not external connections. I had to add the following entry to the cherrypy.config.update
cherrypy.config.update({'server.socket_host': '0.0.0.0',
'server.socket_port': 3000})

Related

How to embed Apache Superset (v1.5) Dashboard in Wordpress?

I'm trying to embed an Apache 1.5 dashboard in a Wordpress site. So far I've succeeded in doing the following:
Upgrade to Apache Superset 1.5 and enable the embedded dashboard functionality
Configure dashboard to be embedded
Implemented backend code to obtain JWT token
What I'm struggling with now is to implement the "#superset-ui/embedded-sdk" functionality in my Wordpress site (using TwentyTwentyOne standard template). I'm trying to follow the instructions listed here: https://fossies.org/dox/apache-superset-1.5.0-source/md_superset_embedded_sdk_README.html
The CDN approach looks the easiest, but I'm getting the error ReferenceError: Buffer is not defined. And if I add the following code:
<script src="https://unpkg.com/buffer"></script>
Then I get the error ReferenceError: require is not defined. I'm not sure how I can resolve this error. Is the CDN approach a dead end, or is there a way I can make this work?
With the "Using npm" approach I'm struggling even more. I'm able to install the npm packages on bitnami linux, but then I don't know how to make use of them in the site. I wasn't able to make it work to add '#superset-ui/embedded-sdk' using wp_enqueue_script(). Also I tried the following code in wordpress:
<script type="module">
import {embedDashboard} from 'http://[MY_IP_ADDRESS]/wp-content/themes/twentytwentyone/node_modules/#superset-ui/embedded-sdk/lib/index.js';
</script>
However then I get the following error:
Uncaught SyntaxError: The requested module 'http://[MY_IP_ADDRESS]/wp-content/themes/twentytwentyone/node_modules/#superset-ui/embedded-sdk/lib/index.js' does not provide an export named 'embedDashboard'
I don't quite understand this error as embedDashboard does appear to be defined in the js file. I also tried using the embedded-sdk/bundle/index.js file with the same result.
I've spent many hours on this and I'm not sure what approach I should take. Can anyone point me in the right direction? Or even better: have a solution to a similar problem with sample code I can look at? If I need to provide more details on what I've tried or what errors I'm seeing let me know.
This is how I did it.
I wrote my backend in Flask.
My Superset Version is 2.0
backend.py
from flask import Flask, render_template, jsonify
import requests
import json
import os
app = Flask(__name__)
#app.route('/')
def hello():
return render_template('index.html')
#app.route("/guest-token", methods=["GET"])
def guest_token():
url = "http://<IP>:<PORT>/api/v1/security/login"
payload = json.dumps({ "password": "<>", "provider": "db", "refresh": "true", "username": "<>" })
headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' }
responsel = requests.request("POST", url, headers=headers, data=payload)
print(responsel.text)
superset_access_token = json.loads(responsel.text)['access_token']
payload = json.dumps ({
"user": {
"username": "kashew",
"first name":"Kashish",
"lastname":"Bakshi"
},
"resources": [{
"type": "dashboard",
"id": "8f96cc84-7e9e-4f5c-ba92-3a1f0825fe3d"
}],
"rls": []
})
bearer_token = "Bearer " + superset_access_token
print(bearer_token)
response2 = requests.post(
"http://<IP>:<PORT>/api/v1/security/guest_token",
data = payload,
headers = { "Authorization": bearer_token, 'Accept': 'application/json', 'Content-Type': 'application/json' })
print(response2.json())
return jsonify(response2.json()['token'])
if __name__ == "__main__":
app.run(debug=True)
Explanation:
The Major part of understanding is the Guest_Token Function.
Here two POST Requests are at play.
The first Request gets the access token from Superset.
The Second request uses the Access Token we got as Bearer and is a POST Call to get the Guest_Token from Superset.
Payload for Guest_Token:
{
"user": {
"username": "kashew",
"first name":"Kashish",
"lastname":"Bakshi"
},
"resources": [{
"type": "dashboard",
"id": "8f96cc84-7e9e-4f5c-ba92-3a1f0825fe3d"
}],
"rls": []
}
Explanation:
The Payload is a JSON with 3 Key-Value Pair.
user (Here, I created a Dummy User{ This can be replaced with your App Auth Logic})
resources (Refer this)
rls (I kept it empty but more can be found here)
I ran my backend on Localhost:5000.
For Frontend, I created a directory "templates" and put my index.html file inside it.
index.html
<html>
<head>
<script src="https://unpkg.com/#superset-ui/embedded-sdk"></script>
<style>
iframe {
width: 100%;
height: 50%;
border: none;
pretext {
margin-right: 10%;
margin-left: 10%;
font-family: Tahoma;
</style>
</head>
<body>
<div class="pretext">
<h2> Covid Statistics </h2>
<p> Dive into Covid data
<p id="dashboard-container"></p>
<script>
async function fetchGuestTokenFromBackend() {
let response = await fetch('http://127.0.0.1:5000/guest-token');
let data = await response.json()
return data
}
const myDashboard = supersetEmbeddedSdk.embedDashboard({
id: "8f96cc84-7e9e-4f5c-ba92-3a1f0825fe3d",
supersetDomain: "http://<IP>:<PORT>",
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
hideTitle: true,
hideChartControls: true
},
});
</script>
</div>
</body>
</html>
Explanation:
It calls my guest-token method which returns a Guest-Token to my frontend. The Frontend then makes a request to Superset Embed Dashboard URL with the guest-token which in turn embeds and renders the chart in an Iframe on my website.
Hello, I hope this example of using 'superset embedded-sdk' will help you:
<!DOCTYPE html>
<head>
...
<script src="https://unpkg.com/#superset-ui/embedded-sdk"></script>
</head>
<body>
<p id="dashboard" width="700" height="700"></p>
<script>
async function getToken() {
const res = await fetch('http://localhost:3000/token');
const data = await res.json()
console.log(data['value']);
return data['value'];
}
supersetEmbeddedSdk.embedDashboard({
id: "7b5ee110-435b-4ad1-a257-97ea340bf82d",
supersetDomain: "http://localhost:8088",
mountPoint: document.getElementById("dashboard"),
fetchGuestToken: () => getToken(),
dashboardUiConfig: {
hideTitle: true,
filters: {
expanded: true
}
}
});
</script>
</body>
</html>

FastAPI - Uploading multiple files using Axios raises Bad Request error

Client code:
!<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<form id="uploadForm" role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file" multiple>
<input type=button value=Upload onclick="uploadFile()">
</form>
<script type="text/javascript">
function uploadFile() {
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("images", imagefile.files);
axios.post('http://127.0.0.1:8000/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
</script>
</body>
</html>
Server code:
from fastapi import FastAPI, File, UploadFile, FastAPI
from typing import Optional, List
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
...
def save_file(filename, data):
with open(filename, 'wb') as f:
f.write(data)
print('file saved')
#app.post("/upload")
async def upload(files: List[UploadFile] = File(...)):
print(files)
for file in files:
contents = await file.read()
save_file(file.filename, contents)
print('file received')
return {"Uploaded Filenames": [file.filename for file in files]}
I get the following error:
←[32mINFO←[0m: 127.0.0.1:10406 - "←[1mPOST /upload HTTP/1.1←[0m" ←[31m400 Bad Request←[0m
I have tried to upload a single file via form action and all works fine, but I need to upload two files.
First, when uploading files or form data, one should use the same form key defined in their endpoint/route. In your case, that is files. Hence, on client side you should use that key instead of images.
Second, the way to upload multiple files is to loop through the array of files (i.e., imagefile.files in your code) and add each file to the FormData object.
Third, the error seems to occur due to some bug/changes in the 0.27.1 version of Axios. Here is a recent related issue on GitHub. Using the latest version, i.e., 0.27.2, from cdnjs here resolves the issue.
Alternatively, you could use Fetch API, similar to this answer (you can add the list of files in the same way as shown in the Axios example below). In Fetch API, when uploading files to the server, you should not explicitly set the Content-Type header on the request, as explained here.
Working Example (using Axios)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Upload Files</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
</head>
<body>
<input type="file" id="fileInput" multiple><br>
<input type="button" value="Upload" onclick="uploadFile()">
<script type="text/javascript">
function uploadFile() {
var fileInput = document.querySelector('#fileInput');
if (fileInput.files[0]) {
var formData = new FormData();
for (const file of fileInput.files)
formData.append('files', file);
axios({
method: 'post',
url: '/upload',
data: formData,
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
}
</script>
</body>
</html>
Starting from Axios v 0.27.2 you can do this easily:
axios
.postForm("https://httpbin.org/post", document.querySelector("#fileInput").files)
All the files will be submitted with files[] key.
More verbose example:
axios.postForm("https://httpbin.org/post", {
"myField": "foo"
"myJson{}": {x:1, y: 'bar'},
"files[]": document.querySelector("#fileInput").files
})

How to sens and access parameters with POST method

I'm creating an authent form for my web page with Javascript and using Vue JS on client side and NodeJS with ExpressJS on server side.
On server side I writed my post method :
server.app.post("/login", function(req, res) {
console.log(req.body.username);
})
On client side, I have my form on my html link to a Vue instance:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="authent">
<p>Username :</p>
<input type="text" name="username" v-model="username" required>
<p>Pwd :</p>
<input type="password" name="password" v-model="password" required>
<button v-on:click="authent">Login</button>
</div>
<script src='/authent/vue.js'></script>
<script src='/authent/vue-resource.min.js'></script>
<script src="authent.js"></script>
</body>
</html>
window.onload = function () {
var authent = new Vue({
el: '#authent',
data: {
username: null,
password: null
},
methods: {
authent: function() {
try {
this.$http.post('/login', {"username": authent.username, "password": authent.password})
.then(function(response) {
console.log(response.body);
})
} catch (e) {
console.log(e);
}
}
}
})
}
Apparently I wrongly send parameters in vue instance because on server side the req.body is undefined.
EDIT
Parameters are sent, but I don't know how I can access them in my post method on server side.
you send it through ajax way. and you probably also send it as json object not a real post data. in express you should write (to have a json parser)
app.use(express.json());
and just to be safe. add a body parser too :-)
app.use(express.urlencoded({ extended: true }))
you probably will need it too sooner or latter
just add the above middlewares
I'm not familiar with Vue, but can you try to use this.username instead of authent.username ?
In your devTools, can you see that params has been seend ? To be sure that's a front-end issue.

JQuery get to .Net Web Api using AAD throws 401 invalid_token / the issuer is invalid

I'm trying to create a simple .Net Core Web Api in Azure to test authentication using JQuery. I managed to resolve the CORS issue but I keep getting a 401 "the issuer is invalid" error when trying to use the bearer token. I was able to test the Web Api using Postman and a secret but not when using JQuery and AAD. I lifted some demo SPA code from a sample that works separately but not when I keep the client and Web Api in separate projects. I thought maybe it was required for me to use the client ID of the Web Api to get my token but that doesn't seem to have any effect. The controller couldn't be any more basic.
namespace test_core_web_api_spa.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
}
}
And the startup for the Web Api is basic.
namespace test_core_web_api_spa
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseCors(options => options.WithOrigins("https://localhost:44399", "https://localhost:44308").AllowAnyMethod().AllowAnyHeader());
app.UseMvc();
}
}
}
The HTML page was copied from a SPA demo using AAD.
<!DOCTYPE html>
<html>
<head>
<title>Test API Call</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse"
data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/#Home">test api call</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>Home</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="app-user navbar-text"></li>
<li>Logout</li>
<li>Login</li>
</ul>
</div>
</div>
</div>
<div id="divHome" class="container-fluid">
<div class="jumbotron">
<h5 id="WelcomeMessage"></h5>
<div class="text-hide">Surname: <span id="userSurName"></span><span id="userEmail"></span></div>
<h2>test page</h2>
</div>
<div>
<br />
Call Api
<br />
<p class="view-loading">Loading...</p>
<div class="app-error"></div>
<br />
<span id="data-container"></span>
</div>
<br />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://alcdn.msauth.net/lib/1.1.3/js/msal.js" integrity="sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ" crossorigin="anonymous"></script>
<script src="js/rest_api.js"></script>
</body>
</html>
And the Javascript was also borrowed from the SPA AAD demo in GitHub.
// the AAD application
var clientApplication;
(function () {
console.log("document ready done");
window.config = {
clientID: 'clientidof_web_api_in_azure'
};
const loginRequest = {
scopes: ["openid", "profile", "User.Read"]
};
const tokenRequest2 = {
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa"]
};
var scope = [window.config.clientID];
const msalConfigDemo = {
auth: {
clientId: "myclientid",
authority: "https://login.microsoftonline.com/mytenantid",
consentScopes: ["user.read","https://myportal.onmicrosoft.com/test_core_web_api_spa/user_impersonation"],
validateAuthority: true
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: false
}
};
function authCallback(errorDesc, token, error, tokenType) {
//This function is called after loginRedirect and acquireTokenRedirect. Not called with loginPopup
// msal object is bound to the window object after the constructor is called.
if (token) {
log("authCallback success");
console.log({ 'token': token });
console.log({ 'tokenType': tokenType });
}
else {
log(error + ":" + errorDesc);
}
}
if (!clientApplication) {
clientApplication = new clientApplication = new Msal.UserAgentApplication(msalConfigDemo, msalConfigDemo, authCallback);
} else {
console.log({ 'clientApplication': clientApplication });
}
// Get UI jQuery Objects
var $panel = $(".panel-body");
var $userDisplay = $(".app-user");
var $signInButton = $(".app-login");
var $signOutButton = $(".app-logout");
var $errorMessage = $(".app-error");
var $btnCallApiTest = $(".btnCallApiTest");
onSignin(null);
// Handle Navigation Directly to View
window.onhashchange = function () {
loadView(stripHash(window.location.hash));
};
window.onload = function () {
$(window).trigger("hashchange");
};
$btnCallApiTest.click(function () {
call_api_test();
});
// Register NavBar Click Handlers
$signOutButton.click(function () {
clientApplication.logout();
});
$signInButton.click(function () {
clientApplication.loginPopup(loginRequest).then(onSignin);
});
function stripHash(view) {
return view.substr(view.indexOf('#') + 1);
}
function call_api_test() {
// Empty Old View Contents
var $dataContainer = $(".data-container");
$dataContainer.empty();
var $loading = $(".view-loading");
clientApplication.acquireTokenSilent(tokenRequest2)
.then(function (token) {
getTodoList(token.accessToken, $dataContainer, $loading);
}, function (error) {
clientApplication.acquireTokenPopup(tokenRequest2).then(function (token) {
getTodoList(token.accessToken, $dataContainer, $loading);
}, function (error) {
printErrorMessage(error);
});
});
}
function getTodoList(accessToken, dataContainer, loading) {
// Get TodoList Data
let urlstring = 'https://localhost:44363/api/values';
console.log({ 'accessToken': accessToken });
$.ajax({
type: "GET",
url: urlstring,
headers: {
'Authorization': 'Bearer ' + accessToken,
},
}).done(function (data) {
// Update the UI
console.log({ 'data': data });
loading.hide();
dataContainer.html(data);
}).fail(function (jqXHR, textStatus) {
printErrorMessage('Error getting todo list data statusText->' + textStatus + ' status->' + jqXHR.status);
console.log({ 'jqXHR': jqXHR });
loading.hide();
}).always(function () {
// Register Handlers for Buttons in Data Table
//registerDataClickHandlers();
});
}
function printErrorMessage(mes) {
var $errorMessage = $(".app-error");
$errorMessage.html(mes);
}
function onSignin(idToken) {
// Check Login Status, Update UI
var user = clientApplication.getUser();
if (user) {
$userDisplay.html(user.name);
$userDisplay.show();
$signInButton.hide();
$signOutButton.show();
} else {
$userDisplay.empty();
$userDisplay.hide();
$signInButton.show();
$signOutButton.hide();
}
}
}());
Logging in does work with AAD and pulls back my email address. I do see a token created and being passed to the Web Api. But then it gives the "issuer is invalid" 401 error. I'm the client Id of the Web Api when making my token request so I'm not sure what else could be changed.
Per comments I have tried to pass a scope to the loginPopup call.
$signInButton.click(function () {
clientApplication.loginPopup(requestObj).then(onSignin);
});
However the only value that works gives the same results:
var requestObj = ["web-api-client-id"];
I've tried the URL of the local web service running including combinations using https://localhost:44399/.default but that throws immediate errors before getting a token with a message like the resource principal named https://localhost:44399 was not found in the tenant. If the problem is the scope setting in this call then I am not sure what value to use to make this work locally when debugging. As a side note I found other Github samples using a format of
var requestObj = {scopes: ["api://clientid/access_as_user"]};
but these fail to execute saying API does not accept non-array scopes. I might ask this in a separate thread.
Update Nov 13
I switched from 0.2.3 of MSAL to 1.1.3 and then updated the logic to reflect the changes made in the different versions.
I also confirmed the client app has API permissions to the web api. I added a new scope to the web api called "user_impersonation". The existing "api-access" was locked to admin control in my tenant.
When trying to use the form "api//" it does not find the resource. Here are the values I tried which all get the same error. I think this format is legacy.
scopes: ["api://web-api-clientid"]
scopes: ["api://web-api-clientid/api-access"]
scopes: ["api://web-api-clientid/user_impersonation"]
scopes: ["api://web-api-clientid/.default"]
ServerError: AADSTS500011: The resource principal named api://web-api-clientid was not found in the tenant named my-tenant-id. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
When trying these scopes the error was 401 audience is invalid.
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa/user_impersonation"]
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa/.default"]
401 www-authenticate: Bearer error="invalid_token", error_description="The audience is invalid"
"aud": "https://myPortal.onmicrosoft.com/test_core_web_api_spa"
When trying these scopes the error message gets my client app correct but again doesn't seem to think my web api app exists on my portal.
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa"]
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa","user_impersonation"]
ServerError: AADSTS650053: The application 'demoapp-frontend' asked for scope 'test_core_web_api_spa' that doesn't exist on the resource 'myportal_guid'.
Sorry for all the confusion but I've been trying everything and the code is getting messy. I'm almost to the point where I might need to start over again.
Modify your code as below
window.config = {
clientID: 'clientidof_web_client'
};
var scope = ["api://{clientidof_web_api}/.default"];
You can check the token by decoding it. The value of aud should be api://client_id_of_web_api
Update:
Have you added your api to your client app permission?
The problem was the configuration data for the Web API. When they say the ClientId what they really want is the value under the "expose an API" option where it says "Application ID URI". What I was putting in there was the guid for the Web Api application registration. Below is how it should look.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "myportal.onmicrosoft.com",
"TenantId": "mytenant-guid",
"ClientId": "https://myportal.onmicrosoft.com/test_core_web_api_spa"
},

Keycloak login returns 404 using JavaScript adapter

I'm using Keycloak's bower package to create a very basic demo HTML/JS app. I have Keycloak running locally and keycloak.init() seems to work (no error triggered). However when I call keycloak.login() a 404 is returned. Might the login URL be wrongly created by the adapter?
The URL returned by keycloak.createLoginUrl() is
https://<keycloak url>/realms/<realm>/protocol/openid-connect/auth?client_id=account&redirect_uri=file%3A%2F%2F%2FUsers%2Fjgallaso%2FProjects%2Fdemos%2Fkeycloak-simple-web-client%2Findex.html&state=b167dc0b-3e5b-4c67-87f7-fd5289fb7b8f&nonce=1e2cb386-51db-496a-8943-efcf4ef5d5e1&response_mode=fragment&response_type=code&scope=openid
And this is my entire code:
<head>
<script src="bower_components/keycloak/dist/keycloak.min.js"></script>
</head>
<body>
<button id="login">Login</button>
</body>
<script>
var keycloak = Keycloak({
url: 'https://keycloak-keycloak.192.168.37.1.nip.io',
realm: 'demo',
clientId: 'account'
});
keycloak.init()
.success(authenticated => {
document.getElementById("login")
.addEventListener("click", () => { keycloak.login(); });
}).error(err => {
console.log("init, error: " + err);
});
</script>
</head>
Response is a plain:
ERROR 404: Not Found
You have 2 posibilities :
invoque login automatically in init method
login manually after call init without params
1)
<head>
<script src="bower_components/keycloak/dist/keycloak.min.js"></script>
</head>
<body>
<button id="login">Login</button>
</body>
<script>
var keycloak = Keycloak({
url: 'https://keycloak-keycloak.192.168.37.1.nip.io',
realm: 'demo',
clientId: 'account'
});
keycloak.init('login-required')
.success(function(authenticated) => {
}).error(err => {
console.log("init, error: " + err);
});
</script>
</head>
2)
keycloak.init().success(function(authenticated) {
if(authenticated == true){
alert('usuario logeado');
}else{
alert('usuario no logeado');
keycloak.login();
}
}).error(function() {
alert('failed to initialize');
});
I had trouble trying directly from the management.
file://c:/example.html
To do a better test you should leave your index.html on a local test server.
What I did was install the web server plugin for chrome and it worked for me.
I hope it'll help you.
regards

Categories

Resources