I have an app that gets bearer token from Azure ad with adal-angular.
I can ask questions to graph-api to get the data i want with that token.
But now i want to change that data. For that i need an app token from azure AD.
Graph api resource is https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/educationschool_update
My app is an React app that uses adal-angular.
My app have an secret to do this.
My question(s) is, can Adal -angular get me this app-token? And if its possible, how can it do that?
below is my config for adal angular. client_secret is what im trying to get to work..
window.authContext = new AuthContext({
tenant: 'xxxxx',
clientId: 'xxxx',
redirectUri: "http://localhost:3000/",
client_secret : 'APP SECRET HERE!?!?!?!?!?!?',
cacheLocation: 'localStorage'
});
this is my method to get token for the logged in person.
aquireToken() {
let header = null;
window.authContext.acquireToken('https://graph.microsoft.com', (error, token) => {
header = {
headers: {
Authorization: 'Bearer ' + token
}
}
});
return header;
}
I think it is not possible from the web client (UI loaded in the browser) because according to Azure AD CORS policy only Authorize endpoint is open to the web clients and you can't send client_secret to the Authorize endpoint. There is no such parameter according to the documentation: https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code
I hope it helps even it came 6 months after asking :)
Related
I have been having trouble on my MERN application I'm building. I am storing a token in cookies when I register/login a user. I am able to access this cookie through my express app fine and when using Postman all works as well.
Now the issue that I am encountering is when I try to access protected routes through my client side that is in React. I'm not sure how to handle this correctly because in my express app I can't get the cookie in the same matter I am doing so when using postman for example. I am using httpOnly: true so that the cookie can only be access from my express app. I want to keep this the way it is with httpOnly for security reasons so I do not want access to the token through my client side.
Here is my code on the express app...
exports.protect = catchAsync(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
console.log(req.headers);
if (!token) {
return next(new AppError('No token found!', 401));
}
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
const freshUser = await User.findById(decoded.id);
if (!freshUser) {
return res.status(401).json({
status: 'fail',
message: 'This token no longer exists!',
});
}
req.user = freshUser;
next();
});
When I try to access the token using req.headers.authorization using postman it works but like I mentioned before using my client side react app it doesn't and is undefined. using req.headers I can see cookies though. I feel like I can just access req.headers.cookies then but what should I do when just running my backend without running my client side app? I do not want to have separate code. Please let me know if I need to clarify what I am saying here.
Got some extent of your question.
First your node server and your react server won't be running on a same port. So, server cookies won't work like it normally should. You should set domain as well. And if your react server and node server run on that domain and it's domain then they can share the cookie.
Example of sending request to node server with authorization header through axios:
axios.post("https://<api>.com", {
headers: {
Authorization: 'Bearer <your-bearer-token>'
}
})
postman sends it this way.
I'm developing an SPA with React that is hosted as an Azure Static Web App. The App is secured with Azure AD Authentication which works great, I already built a Login that works fine and I can call Azure (Graph) APIs with the token I got and retrieve information for the granted scopes (e.g. user profile picture). To achieve this, I'm using a wrapper called React AAD MSAL which neatly wraps the Microsoft Authentication Library (msal#1.4.0).
So far so good, no problems here. But I'm in need of a backend, of course. I decided to this with Azure Functions, since serverless is the best way for me here. So I made a quick HTTP trigger protoype that runs in Azure as Azure Function und works when I call the URL with the correct paramters.
But of course the Azure Function needs to be secured, so only my React App can call this function. So I thought there should be way to do this through Azure AD, since my user is already logged in as such.
I tried and tried and tried different ways I found online but none of them seem to work or I am doing something wrong.
The general tutorial I tried to follow is this one from MS itself. I tried using the "Express" setting which of course didn't work. I tried the advanced configuration, which also didn't work. The advanced tutorial says you need to have an App registration for the service, I'm not even sure if this is can be my Static Web App or a new on (I tried both with no success). Isn't it enough to tell the Azure Function that it is now AAD secured and may only accept calls from a source secured by an access token that contains the App ID of my app, which is provided in the settings? You can easily provide all these settings, it just doesn't seem to work.
So I'm stalling very early on here. To call the function itself, I first need to get a Authorization Token. According to this tutorial from MS (see "Validate tokens from providers"), I need to send an access token which I got when logging in to my SPA Web App to the Azure Function endpoint ending in .auth/login/aad. Getting this token is easy, since React AAD MSAL provides a method authProvider.getAccessToken() which I can use to extract it. I'm then making a POST request to https://<My Azure Function URI>/.auth/login/aad with the access token in the body as JSON { 'access_token': authToken.accessToken }. I should be getting an Authentication Token which I can then use to call the actual function, but I always get the same response, no matter what I try: You do not have permission to view this directory or page.
So this is where I am. I tried different methods and solutions I found to no avail. Maybe I did something wrong from the ground up, maybe I'm using the wrong methods, I really don't know at this point. Does anyone have experience with this? Is there something wrong in my general approach, do I need to do something else? Or is it just something in the configuration I need to change?
Edit
Since it was asked, here's how I retrieve the token. The concept behind this is using a redux-thunk to dispatch an asynchronous action to the react-redux store. I simplified it not only for this question here but for my testing as well. Right now I'm only trying to get the authentication token and log the answer the POST request is giving me:
import { authProvider } from '../../Authentication/AuthProvider';
//Fetch
async function getAccessToken(authToken) {
const body = { 'access_token': authToken.accessToken };
fetch('https://<My Azure function URL>/.auth/login/aad', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
},
).then(response => {
console.log(response);
});
}
export const fetchAddressData = () => async dispatch => {
const token = await authProvider.getAccessToken();
await getAccessToken(token);
// The actual call to the Azure function will go here once we have an Authentication Token
}
The authProvider is a component from react-aad msal and the configuration looks like this:
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
//MSAL Config
const config = {
auth: {
authority: '<Tenant ID>',
clientId: '<Client ID from App registration (Azure Static Web App)>',
redirectUri: window.location.origin
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
'openid',
'user.read',
'https://<Azure Function URI>/user_impersonation'
],
forceRefresh: true
}
// Options
const options = {
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)
Edit 2
I tweaked some additional settings trying to work with the user impersonation, still no success. Here's an overview over my current Azure settings that are important for this (did I forget any?).
Azure Function:
Authentication is activated, AAD auth only, advanced settings:
Azure Function - App Registration:
Authentication settings:
Client secret:
Expose an API - Exposing user_impersonation API so the Web App can consume it:
Azure Static Web App (React SPA) - App Registration:
Application URI ID which is used as Token Audience in the Azure Function (advanced authentication setting):
API permissions - using the user_impersonation API which is exposed by the Azure Function App Registration:
Is there anything wrong in this configuration? It mostly likely is, but I don't know what since I followed the tutorial on MSDN. I only added the user_impersonation afterwards since it didn't work.
According to the information provided, you do not configure right scope in your authProvider file. You need to add the scope you define when you create AD application to protect function. So please update the scope as scopes: ["openid","<your function app scope>"] in authProvider.
For example
Create Azure AD application to protect function
Register Azure AD application. After doing that, please copy Application (client) ID and the Directory (tenant) ID
Configure Redirect URI. Select Web and type <app-url>/.auth/login/aad/callback.
Enable Implicit grant flow
Define API scope and copy it
Create client secret.
Enable Azure Active Directory in your App Service app
Create Client AD application to access function
Register application
Enable Implicit grant flow
configure API permissions. Let your client application have permissions to access function
Code
authProvider
import { MsalAuthProvider, LoginType } from "react-aad-msal";
import { Logger, LogLevel } from "msal";
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: "https://login.microsoftonline.com/<tenant>",
clientId: "<>",
postLogoutRedirectUri: window.location.origin,
redirectUri: window.location.origin,
validateAuthority: true,
navigateToLoginRequestUrl: false,
},
system: {
logger: new Logger(
(logLevel, message, containsPii) => {
console.log("[MSAL]", message);
},
{
level: LogLevel.Verbose,
piiLoggingEnabled: false,
}
),
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
},
},
{
scopes: [
"openid",
"<the scope you define for your function>",
],
forceRefresh: true,
},
{
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + "/auth.html",
}
);
Call API
const CallAPI= async () => {
// You should should use getAccessToken() to fetch a fresh token before making API calls
const authToken = await provider.getAccessToken();
console.log(authToken.accessToken);
let body = { access_token: authToken.accessToken };
let res = await fetch(
"<your function url>/.auth/login/aad",
{
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
let data = await res.json();
console.log(data);
body = { name: "Azure" };
res = await fetch("<>", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-ZUMO-AUTH": data["authenticationToken"],
},
body: JSON.stringify(body),
});
data = await res.text();
console.log(data);
};
I was dealing with the same issue for a while. If your sure you are getting the right access token and and passing it correctly, then look into the configuration in the portal. If you automatically created the app registration for the function app, Check how the ISSUER URL is set up. You can find this in the function app>authentication>edit. make sure that the url does not have /v2.0 at the end. Azure function only work with the default(/v1.0) route.
I'm building a SPA using Angular.js and ASP.NET and I would like to know what is the best way to secure it.
Here is what I need :
I would like to use MVC framework to hide my application only to logged users. So the first thing that users will do before launching the SPA will be to log into the website using a simple login form.
When the Angular app will be launched, it will communicate with my ApiController using REST requests.
I also want my user to be logged out automatically after 20 minutes of inactivity.
I know that REST is supposed to be stateless... but I can't figure how to implement all I need without sessions...
But on the other side, I want to be able to use my WebAPI with a future mobile application. I will have to use Tokens for the authentication on this application.
What is the best way for me to achieve that kind of authentication?
Thanks for your time!
I developed an entire security layer with the same conditions as yours following those very well explained in this post here.
BTW, the token will expire automatically after 20 minutes because when you create it you will set it's expiration date immediately; every time you're going to make a request, the system will check the token exp date with the current date, refusing your token if the time passed. For example this a tipical oauth server configuration with token and refresh token settings:
internal static OAuthAuthorizationServerOptions GetAuthorizationServerOptions(IComponentContext scope)
{
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
ApplicationCanDisplayErrors = true,
TokenEndpointPath = new PathString(Constants.PublicAuth.OAUTH_TOKEN_PATH),
AuthorizeEndpointPath = new PathString(Constants.ExternalAuth.AUTH_ENDPOINT),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(Constants.PublicAuth.TOKEN_EXPIRATION_IN_MINUTES),
Provider = scope.Resolve<AuthorizationServerProvider>(),
AccessTokenFormat = new CustomJwtFormat(),
RefreshTokenProvider = scope.Resolve<SimpleRefreshTokenProvider>()
};
return oAuthServerOptions;
}
The refresh token is also very useful, but you have to manage the token replacement by yourself; for example in our application we pass every API call through a single service that, if the server responds 401 (unauthorized), it will try to request a new token using the refresh token and then it will try the same call again. Only after the second failure you'll be redirected to the login page.
For example:
function executeCallWithAuth(method, url, payload, params) {
var defer = $q.defer();
debug.logf('{0}: {1}', method.toUpperCase(), url);
$http({ method: method, url: url, data: payload, headers: createHeaders(), params: params }).then(
function(results) { defer.resolve(results); },
function(error) {
if (error.status !== 401) defer.reject(error);
else {
debug.warn(`Call to: ${method}:${url} result in 401, try token refresh...`);
auth.refreshToken().then(
function() {
debug.warn('Token refresh succesfully, retry api call...');
$http({ method: method, url: url, data: payload, headers: createHeaders() }).then(
function(results) { defer.resolve(results); },
function(errors) { defer.reject(errors); });
},
function(tokenError) {
debug.warn('Token refresh rejected, redirect to login.');
$state.go('login');
defer.reject(tokenError);
});
}
});
return defer.promise;
}
and
function createHeaders() {
var headers = {
};
var authData = storage.get('authorizationData');
if (authData) {
headers.Authorization = 'Bearer ' + authData.token;
}
return headers;
}
Using Angular the best way to secure a route is "do not create a route". Basically, you need to load the user profile, and only after that you will create the routes only to the pages he can navigate to. If you don't create the route for a page you don't need to secure that page: Angular will automatically send the user to a 404.
I would secure your WebAPI calls with OAuth2 (you can even use the built in Identity 2.0 provider that comes baked in with it). Keep your WebAPI stateless, use SSL (consider a filter to force it), and use the [Authorize] tags to secure you services. On the MVC side, this will have to maintain state and you will want to have the login form get an OAuth2 token from your WebAPI layer and pass that down into Angular. Set the expiration on this to 20 minutes. You can also use the cookies authentication model here since it will need to be stateful on the MVC side, but all ajax calls made to the WebAPI layer by Angular will need to pass the OAuth2 token as a bearer token in the Authorization request header.
We are transitioning from an API using cookies for state (ExpressJS sessions) to a stateless (token) API.
We use a single PassportJS authentication strategy (GoogleStrategy). When the OAuth flow completes, Google calls back to a backend route with an access token.
Previously, we would set a cookie at this point using req.session and redirect the user to our dashboard.
With a token API, we generate a token based on the email (acting as a username) and access token (acting as password) when Google calls back to the backend route.
How do we pass this token to the front-end (Angularjs) so that it can make authenticated requests?
Do we need to switch to Google's front-end OAuth APIs?
One way to pass the token to a client-side web application is to put the signed JSON web token into the cookie , which your client-side app can access and use (either appending the token to every GET request or using it in your web socket authentication). Just to be clear, you're no longer using cookies as a reference for server-recorded state, instead you're are simply using them as a storage mechanism that both client and server can access, where you can store your encoded token.
Here's a great example from the generator-angular-fullstack:
In the router, receiving the callback from the google authentication:
.get('/callback', passport.authenticate('google', {
failureRedirect: '/signup',
session: false
}), auth.setTokenCookie);
And the setTokenCookie function:
function setTokenCookie(req, res) {
if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'});
var token = signToken(req.user._id, req.user.role);
res.cookie('token', JSON.stringify(token));
res.redirect('/');
}
And the signToken function:
function signToken(id) {
return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 });
}
I'm using express.js, passport with jwt strategy and of course jsonwebtoken for node.js.
So, currently, I've managed to implement a server-side logic, which enables users to login and returns the jwt token.
After that, when I do a get request with the corresponding token in the header, it correctly verifies the jwt token and display the info. The code is as follows:
var jwt = require('jsonwebtoken');
function createToken(user) {
return jwt.sign(user, 'shhhhh', {
issuer: "accounts.examplesoft.com"
});
}
var opts = {};
opts.secretOrKey = 'shhhhh';
opts.issuer = "accounts.examplesoft.com";
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);
User.findById(jwt_payload.id, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
app.post('/jwt_login', function(req, res) {
User._loginJwt({
email: req.body.email,
password: req.body.password
}, function(err, user) {
if (err) res.json(err);
else res.json(createToken(user));
});
});
app.get('/jwt_test', passport.authenticate('jwt', {
session: false
}), function(req, res) {
res.json(true);
});
Now I'm trying to do a client-side page. I'm using angularjs and there are a lot of jwt libraries for angularjs or rather, client side in general. Now I have a series of questions:
First and foremost, is the server-side implement correctly (from what you can tell by the code above)?
Is it safe if I store the jwt token in localStorage (on client-side)?
Why are there so many libraries available for jwt client side? Isn't it enough to get the token and then call the requests with that token? What else could I do with that token on the client side?
Can't somebody just copy the jwt token from the localStorage and make requests as if they're logged in? Isn't that a security issue?
Thanks for your responses!
The server-side implementation looks fine, though the claims in the token could be expanded. Just always authenticate the token and you're good.
Yes. That's part of why JWT is useful. If the user alters the token, it will not match its signature and will fail authentication.
From what I recall, the client-side stuff is for when you pass data in the payload that is used on the client. You want to be able to authenticate the token on that side as well then, so your front-end doesn't do anything it shouldn't.
a. If you just have a RESTful API that validates requests with the token, you don't have to do anything with the JWT on the front-end besides sending it with requests.
Yes. That's why your token should include an expiration in its claims. Keep in mind, the only way that gets into LocalStorage is if they logged in to begin with.
See here for claims that can be in your token:
http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#rfc.section.4