Get userinfo from ADFS 2016, react, ADAL.js - reactjs

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.

Related

Dotnet API requires auth both for application and React

I must be really stupid, But I have been struggling for weeks to try solve this issue, and all the digging I have done (in Stack overflow and MS Documentation) has yielded no results (or I'm too stupid to implement auth correctly)
I have a dotnet service which needs to act as an API - both for an application to post data to (an exe which logs exception data), and for a UI (react app) to get the posted exceptions
the exe can successfully send data to the dotnet app after first getting a token from login.microsoftonline.com and then sending the token (and secret) in the http request.
A sample postman pre-request script of the auth used (I've set all the secret stuff as environment variables):
pm.sendRequest({
url: 'https://login.microsoftonline.com/' + pm.environment.get("tenantId") + '/oauth2/v2.0/token',
method: 'POST',
header: 'Content-Type: application/x-www-form-urlencoded',
body: {
mode: 'urlencoded',
urlencoded: [
{key: "grant_type", value: "client_credentials", disabled: false},
{key: "client_id", value: pm.environment.get("clientId"), disabled: false},
{key: "client_secret", value: pm.environment.get("clientSecret"), disabled: false}, //if I don't configure a secret, and omit this, the requests fail (Azure Integration Assistant recommends that you do not configure credentials/secrets, but does not provide clear documentation as to why, or how to use a daemon api without it)
{key: "scope", value: pm.environment.get("scope"), disabled: false}
]
}
}, function (err, res) {
const token = 'Bearer ' + res.json().access_token;
pm.request.headers.add(token, "Authorization");
});
Now in React, I am using MSAL(#azure/msal-browser) in order to login a user, get their token, and pass the token to one of the dotnet endpoints using axios as my http wrapper, but no matter what I do, it returns http status 401 with WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid".
A simplified code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent(activeAccount).accessToken;
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401
The dotnet service has the following configurations
public void ConfigureServices(IServiceCollection services){
...
var authScheme = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
authScheme.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
...
}
namespace Controllers{
public class EndpointController : ControllerBase{
...
[Authorize]
[HttpGet]
public IActionResult GetEndpoint(){
return Ok("you finally got through");
}
}
}
I've literally tried so many things that I've lost track of what I've done...
I've even cried myself to sleep over this - but that yielded no results
i can confirm that running the request in postman, with the pre request script, it is possible to get the response from the endpoint
So....
After much digging and A-B Testing I was able to solve this issue.
I discovered that I was not sending the API scope to the OAuth token endpoint. To do this I needed to change the input for acquireTokenSilent.
The updated code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent({scopes:["api://XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/.default"],account:activeAccount}).accessToken;//here scopes is an array of strings, Where I used the api URI , but you could directly use a scope name like User.Read if you had it configured
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401

csurf with React: Invalid token after changing user

I've had csrf protection with the csurf module working for a while now on my React SPA. I am also using passport for authentication. I do not do any server-side rendering, so the server sends a csrf token in the response body to the client when it hits the /users/current endpoint, which is protected with csrfProtection, something like this:
import csrf from 'csurf';
const csrfProtection = csrf();
router.get("users/current", csrfProtection, async function(req, res)
{
.....
res.write(JSON.stringify({ ..., csrfToken: req.csrfToken() }));
res.end();
}
On the client side I then add the token to all subsequent request headers, a bit like this:
axiosInstance.get("/users/current")
.then(resJson =>
{
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = resJson.data.csrfToken;
return config;
});
}
My first question is how the first request even manages to pass the csrfProtection without a token in its header. Yet since the token can only be accessed on the server to send to the client if the route is csrf protected, I don't see a way around this, and it does work somehow.
However, recently I have been getting "ForbiddenError: invalid csrf token" when a user logs in or deletes their account. This has only started happening after I upgraded all my node packages to the latest versions. First the client makes a request to /users/login to submit the username & password, and then makes a request to /users/current to get the new csrf token:
axiosInstance.post("/users/login", {
"username": login.username,
"password": login.password
})
.then(async resJson =>
{
// *code to update user details in redux store*
// ......
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = undefined;
return config;
});
return resJson;
})
.then(async resJson =>
{
const { csrfToken } = await axiosInstance.get("/users/current")
.then(resJson => resJson.data);
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = csrfToken;
return config;
});
return resJson.data;
}
I suspect it's something to do with subsequent requests coming from a different userId (which I obtain from req.user[0].userId), with which csurf will not accept the previously issued token. But I have no idea how to issue the new token csurf does expect, to the client. And it still doesn't explain why what I had before has suddenly stopped working since none of my logic has changed. This isn't the kind of error I'd typically expect after package updates.
Here someone mentions you can just set any header on the client and have the server check for that. atm I am adding the csrf token to all the client's request headers and using the csurf module's request handler function to check it, but there is nothing stopping me from writing my own. If this is true, the value of the header doesn't even matter, just that it exists. I am holding off on this option though because I feel there is something basic I'm not understanding about my current setup, which once rectified will mean this can be easily fixed.
Would appreciate any help or explanation! Thanks 🤍

How can I get the JWT token after authenticating?

I have a Rest spring-boot API that when a user authenticates the api returns the token jwt, I noticed in the browser that the token appears in the Response Header> Authentication and in the tests with the Postman it shows in the Body.
How can I get this token to store in the Local Storage browser by Reactjs?
My code that makes the requests looks like this:
import { ACCESS_TOKEN, API_BASE_URL } from '../constants';
export function request (options) {
const headers = {
'Content-Type': 'application/json',
}
if (localStorage.getItem(ACCESS_TOKEN)) {
headers.append('Authorzation', 'Bearer ' + localStorage.getItem(ACCESS_TOKEN))
}
return fetch(API_BASE_URL+options.url, {
method: options.method,
headers: headers,
body: options.body
})
.then(function(response){
// Falta pegar o token e gravar na local estorage
if (!response.ok) {
return Promise.reject(json);
}
return json;
});
};
// Save data to the current local store
localStorage.setItem("username", "John");
// Access some stored data
alert( "username = " + localStorage.getItem("username"));
The first argument of setitem is the key
My account got blocked by some down votes questions, the funny thing is I have to re-edit them, even though I already have the accepted answer.I do not understand what's the point to do this.I am so frustrated by this stackoverflow system.
Now, I basically can do nothing but keep editing my questions, and they have all been answered. This is ridiculous !!!

Active Directory Authentication with .NET Core Web API and React

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!

read objects in ibm cloud object storage using api key

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

Resources