authenticate Azure AD with Node.js via oauth2 - node.js

I want to authenticate users in Azure active directory with node js (I'm really new in azure and active directory), I read a lot of documentation, there exists two ways for doing it.
first: my web request authenticate through a form that Microsoft provides me, then user log-in and this redirect to a URL of mine <--- this way I don't need
second: (this is the way I need) I'm using Oauth2, with
var BearerStrategy = require('passport-azure-ad').BearerStrategy for authenticate, I have my client id, tenanId, client_Secret etc.
As a first step I need get an Acces_token which I get send request to this URL via postman:
https://login.microsoftonline.com/My_alias_tenan/oauth2/token
I send these params on my body:
{grant_type: client_credentials, client_id: 1f7bbc3e-19ed-4ae5-b16d..., client_secret: 98ijhi7tuf..., resource: https://management.azure.com/, .... }
I just follow this blog: https://blog.jongallant.com/2017/11/azure-rest-apis-postman/
I received a token like this:
"token_type": "Bearer",
"expires_in": "3600",
"ext_expires_in": "3600",
"expires_on": "1570045543",
"not_before": "1570041643",
"resource": "https://management.azure.com/",
"access_token": "eyJ0eXAiOiJKV
which I enter in the header for my next request which is pointing to my localhost: because is here where I have my options to send the authenticate, and are this:
identityMetadata: 'https://login.microsoftonline.com/alias_tenan/v2.0/.well-known/openid-configuration',
clientID: process.env.AD_CLIENT_ID,
audience : 'https://management.azure.com/',
validateIssuer: false,
passReqToCallback: true,
isB2C: false,
allowMultiAudiencesInToken: false,
issuer:null,
loggingLevel: 'info',
loggingNoPII: false,
responseMode: query
I have my code like this:
return passport.authenticate('oauth-bearer', function(req, token, done) {
console.log(token)
res.status(200).json({'name': 'name'});
}
)(req, res, next)
but always received this message:
{"name":"AzureAD: Bearer Strategy","hostname":"DESKTOP-U0R9GTV","pid":1168,"level":30,"msg":"authentication failed due to: error: invalid_token","time":"2019-10-02T22:54:04.782Z","v":0}
Has anyone done this successfully? thanks in advance
and I hope anyone can help me.

Make use of any npm packages, I use https://www.npmjs.com/package/react-aad-msal for single signon.
Another package that would be useful is https://www.npmjs.com/package/#kdpw/msal-b2c-react
Any package relies on MSAL.

Related

Why is the Google OAuth 2.0 flow not returning an id_token when exchanged with the authorization code?

There's a clear diagram of the how this process works in Google's OAuth 2.0 docs. I've implemented the diagram as expected. My app hasn't been verified by Google yet, so the OAuth consent screen shows a warning and only allows whitelisted emails to be used in the OAuth process (until the app is verified). Using my email addresses, the code works perfectly. As soon as I started using some friends' emails for testing (all of which were whitelisted in the Google Developer Console), I'm not receiving the id_token. Interestingly, I'm not seeing any errors. Exchanging the code, I'm still receiving the access_token and refresh_token but not the id_token.
I don't think it's a problem with my code since I receive the id_token when using my personal email accounts, but here's my code anyways.
class GoogleOauthService implements GoogleOauth {
#client;
constructor() {
this.#client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URL
);
}
async getTokens(
params: GoogleOauth.GetTokensParams
): GoogleOauth.GetTokensResult {
const { tokens } = await this.#client.getToken(params.code);
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
scope: tokens.scope,
tokenType: tokens.token_type,
idToken: tokens.id_token,
expiryDate: tokens.expiry_date,
};
}
}
I'm not receiving any errors from the call. tokens results in the following:
{
access_token: 'secret access token',
refresh_token: 'secret refresh token',
scope: 'https://www.googleapis.com/auth/calendar',
token_type: 'Bearer',
expiry_date: 1673742003175
}
If anyone has any idea why Google isn't giving the id_token for some emails, I'd appreciate the insight.
The issue you were having was related to your scope of authorization you are not requesting the proper ones.
Here is a little background information on why you needed to change your scope.
Oauth2 is used for authorization, so if you want to request permission from a user to access their private user data.
I can see that you are requesting the scope of https://www.googleapis.com/auth/calendar You are using Oauth2 to request permission of a user to access their google calendar data this is Oauth2. The response will be an access token granting you access to their google calendar data and a refresh token. This is what Oauth2 responds with
{
access_token: 'secret access token',
refresh_token: 'secret refresh token',
scope: 'https://www.googleapis.com/auth/calendar',
token_type: 'Bearer',
expiry_date: 1673742003175
}
Then we have Sign-in or authentication or Open Id connect which was actually built on top of Oauth2. Open Id connect allows you to verify that the user behind the machine is in fact the owner of the account. This is authencation not authorization. We are authenticating or signing in a user. Open Id connect returns an access token and a refresh token and an id token.
{
"access_token": "[redacted]",
"id_token": "[redacted]",
"expires_in": 3599,
"token_type": "Bearer",
"scope": "openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
"refresh_token": "[redacted]"
}
To use open id connect you need to use the profile, openid, and/ or email scopes. Then you will get back an access token granting you access to use profile information as well as the refrshtoken, and also an id token.
The id token is the key part of open id connect it gives you information on the user itself if you take it over to jwt.io you can decrypt the jwt.
{
"iss": "https://accounts.google.com",
"azp": "407408718192.apps.googleusercontent.com",
"aud": "407408718192.apps.googleusercontent.com",
"sub": "[MyInternalGoogleId]",
"email": "[MyEmailAddress]",
"email_verified": true,
"at_hash": "Je-vk1k8MRaqK0gw3yXj7g",
"name": "Linda Lawton",
"picture": "https://lh3.googleusercontent.com/a/AEdFTp46WTOIbHj5Nl1VJRFf4Izj9YrX61i75F16H1qSA1Q=s96-c",
"given_name": "Linda",
"family_name": "Lawton",
"locale": "en",
"iat": 1673784532,
"exp": 1673788132
}
The Id token allows you to verify after a user has been authenticated that they are in fact the owner of the account and just signed in.
as opposed to authorization which will only give you an access token with no guarantee that the one sending the access token was the user who owns the account, it could be an automated server app which has been granted authorization to access the users data.
I'm now able to retrieve the id_token for accounts that I was previously unable to. The change I made was including the following in my OAuth 2.0 scopes
[
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
];
Once these two scopes were included, the id_token appeared.

Keycloak issue Cannot exchange code for grant in bearer-only mode

I'm new to keycloak and was trying to setup a role based authentication using keycloak
with Nodejs but each time I login with my correct username and password which do exist in realm I get this bearer-only grant issue. I have tried all of the solutions and even checked if the access type on Keycloak is Bearer-only but no that is confidential tried setting the bearer-only to true and false as well but nothing worked for me
{
"realm": "realm_name",
"auth-server-url": "Keycloak_auth_url",
"ssl-required": "none",
"resource": "resource",
"verify-token-audience": false,
"public-client":true,
"grant_type":"password",
"credentials": {
"secret": secret_credentials
},
"confidential-port": 0,
"policy-enforcer": {},
"scope":"openid"
}
This is my keycloak.json file
app.get("/", keycloak.protect(), function (req, res) {
console.log(req)
}
)
This is my simple function for protecting a route
You configured your client to use password grant, which doesn‘t use an authentication code. Using this grant, the client gets an access token and optional a refresh token by posting it‘s clients credentials together with the resource owners credentials to the authorization server.
Depending on the type of application that your client is, choose an according grant type.
Nevertheless password grant shouldn’t be used at all, a recent update of the OAuth 2.0 current best practices stated. See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-18

Confusion with Intuit Oauth authorisation flow: how to I extract auth code and exchange for tokens?

I'm building a React-Node application to access QuickBooks APIs, and my first step is to set up the authorisation flow from my node backend, using the intuit-oauth library.
I'm using the OAuth2.0-demo-nodejs sample app as my template (https://github.com/IntuitDeveloper/OAuth2.0-demo-nodejs).
So the first step is to create an authorisation request. Instead of building UI where users enter their details, I've hardcoded in my app's keys:
app.get("/authUri", urlencodedParser, (req, res) => {
oauthClient = new OAuthClient({
clientId: "*****",
clientSecret: "*****",
environment: "sandbox",
redirectUri: "http://localhost:8000/callback",
});
const authUri = oauthClient.authorizeUri({
scope: [OAuthClient.scopes.Accounting],
state: "intuit-test",
});
res.send(`this is authUri: ${authUri}`);
});
This code is working as it should and is returning authUri. However, what to do next is confusing me. The documentation says that I should receive back an authorisation code which then needs to converted into a token, and in the sample app, the code to perform this is as follows:
app.get('/callback', function(req, res) {
oauthClient.createToken(req.url)
.then(function(authResponse) {
oauth2_token_json = JSON.stringify(authResponse.getJson(), null,2);
})
.catch(function(e) {
console.error(e);
});
res.send('');
});
I've also read in the documentation that I need to redirect users to an authorisation page, create a UI that initiates a redirect, and then get the authorisation code (https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0). So I vaguely understand this but am having knowing where to start after sending the authorization request. Suggestions?
In the OAuth 2.0 Authorization Code flow, after initiating the authorization request, the enduser gets redirected to the login page and enters his credentials. Then he gets redirected to the callback_uri (redirect_uri) with the code parameter. After that, you have to make a Post Request to the /token endpoint and send the code. When all goes correct you get an AccessToken.
Authorization Code Grant

Azure MSAL - Authentication cannot realize userinfo request

I've implemented msal library in my angular application using Implicit Flow. I can login using microsoft ad account and I have the token needed for authentication requests for api usage.
When I decode token using https://jwt.ms/ I can see that aud key has value of my client app id, how should look like the url for userinfo?
If I will use these endpoints which are described in docs I had error with invalid token:
https://graph.microsoft.com/oidc/userinfo or
https://graph.microsoft.com/v1.0/me/
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
"innerError": {
"request-id": "8177b64f-c899-48c3-aceb-xxxxxxxxx",
"date": "2020-03-18T07:41:04"
}
}
}
I've forgot to put my module config.
...
MsalModule.forRoot({
auth: {
clientId: environment.CLIENT_ID,
redirectUri: environment.REDIRECT_URI,
postLogoutRedirectUri: environment.POST_LOGOUT_REDIRECT_URI,
authority: environment.AUTHORITY,
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true,
}
},
{
popUp: false,
consentScopes: [
'user.read',
'openid',
'profile',
],
unprotectedResources: [],
protectedResourceMap: [
['https://graph.microsoft.com/v1.0/me', ['user.read']]
]
})
...
where:
authority url: Authority: 'https://login.microsoftonline.com/xxxxtenant_idxxxx'
client id: App registration client id
If you want to call Microsoft Graph API, you need get the access token with the correct(Microsoft graph api) audience.
When you acquire the access token, you should use https://graph.microsoft.com/User.Read or just User.Read as the scope. You can check the values in app.module.ts
Also, remember to add the User.Read permission for your application on Azure portal. App registrations->your application->API permissions->Add a permission->Choose Microsoft Graph API->Delegated permission->Check the permissions you need.
Update:
Reference:
MSAL Angular to login, logout, protect a route, and acquire an access token for Microsoft Graph.
Solved. The problem is in specified endpoint. When I make request for graph from angular the app interceptor handle this endpoint and use scope (user.read) for graph, and then the correct token will be assigned.
Before I was trying to copy token after authorization and pasting it into postman. So yep, the token was not valid in all.. Thank's for your help.

passport-azure-ad: Why am I getting "jwt audience is invalid"?

(All IDs in the below example are made up. I've added them to illustrate the format for some of the parameters).
My tenant is at acme.co.uk, but I want the web app that I've got deployed at https://subdomain.acme2.co.uk to delegate authentication of users to Azure AD.
I registered my app as below using the new portal:
(The field names are those used on the azure portal).
App ID URL: https://acme.co.uk/11111111-2222-3333-4444-555555555555
Home page URL: https://subdomain.acme2.co.uk/api/auth/aad
Reply URLs: https://subdomain.acme2.co.uk/api/auth/aad/callback
and created a secret key that doesn't expire with the value: '111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaa='
I've been following the passport-azure-ad github sample using v1 endpoint as a guide.
{
allowHttpForRedirectUrl: false,
clientID: 'https://acme.co.uk/11111111-2222-3333-4444-555555555555',
clientSecret: '111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaa=',
identityMetadata: 'https://login.microsoftonline.com/acme.co.uk/.well-known/openid-configuration',
isB2C: false,
issuer: null,
loggingLevel: 'info',
passReqToCallback: false,
redirectUrl: 'https://subdomain.acme2.co.uk/api/auth/aad/callback',
responseMode: 'form_post',
responseType: 'code id_token',
skipUserProfile: true,
useCookieInsteadOfSession: false,
validateIssuer: true
}
When I got to 'https://subdomain.acme2.co.uk/api/auth/aad' in my browser I get redirected to MS to login, which I then do, but when the redirect back to 'https://subdomain.acme2.co.uk/api/auth/aad/callback' happens, I'm not authenticated.
Inspecting the headers from the callback POST request I see:
In _validateResponse: jwt audience is invalid. expected: https://acme.co.uk/11111111-2222-3333-4444-555555555555,spn:https://acme.co.uk/11111111-2222-3333-4444-555555555555
Any ideas what I've done wrong??? [:-|
(Aside: Debugging this is a pain, as I'm running in docker and the redirects don't seem to work properly, so I have to deploy to a real environment ATM).
Ok found it...
clientID: 'https://acme.co.uk/11111111-2222-3333-4444-555555555555'
was the culprit.
That should be the app Id NOT the app Id URI. Doh. Many thanks to #Alex Blex for the guidance. This was most helpful.
In my instance, I was using .env and getting the clientId from there.
I solved this issue by remove the quotes around clientID in my config and .env file.

Resources