Azure AD oauth on Chrome Extension using chrome.identity? - google-chrome-extension

I'm seeking some help to understand how to implement an azure oauth login on my chrome webextension. I've found a very useful suggestion here on SO by #Rinor how to use user.identity in with Azure authentication and I think I'm half way to succeed.
Using the code below I get the login popup and I can easily sign-in with username and password. As a response from Azure I get an url that contain the token, I then extranct the token with a regex and I save it to the crhorme.storage.
The problem is that I'm now stuck. How should I then proceed now to get the user details (I only need the email address or the username) actually. I do not understand if now I need another ajax call to /oauth2/v2.0/token to get an additional token...and if so, how should I pass the token I got from the initial call to /oauth2/v2.0/authorize.
Does anyone have any idea on how to proceed? Any help would be more than welcome 🙏
Thanks a lot in advance
chrome.identity.launchWebAuthFlow(
{
url: 'https://login.microsoftonline.com/' + tenant_id + '/oauth2/v2.0/authorize?' + // <= here tenant id or just common
'response_type=token' +
'&response_mode=fragment' +
'&prompt=login' +
'&client_id=' + client_id + // <= here client id from azure console
'&redirect_uri=' + redirectUrl +
'&scope=openid https://management.azure.com/user_impersonation profile',
interactive: true
},
function (responseWithToken) {
// the access token needs to be extracted from the response.
console.log(responseWithToken);
let token = responseWithToken.match(/(?<=access_token=).*(?=&token_type)/);
chrome.storage.local.set({ "azure_token": token }, function () {
console.log('Value is set');
});
}
);
// What next? :S

If you want to get the username of the user, you could decode the access_token what is the response after the user login. And the upn in the claims is the username of the user, maybe a phone number, email address, or unformatted string.
For more details about access token, see here.

Related

Unable to get username in Azure B2C after successful authentication

I have a React SPA that communicates with the backend API (Azure Function App). I've created an app registration for both the SPA and the Azure Function App following the steps outlined here. Both app registrations are hosted in a separate directory from the Azure Function app since I'm using AD B2C. I'm able to successfully authenticate the user and make requests to the backend. I'm using PKCE as the auth protocol and MSAL.js to manage the authentication flow.
I've configured a standard signup/signin policy for which I'm using Local Account as the Identity provider and username as the user id.
Here's what the login screen looks like:
Here's the relevant code from the SPA which handles auth:
const { instance, accounts, inProgress,} = useMsal();
if (accounts.length > 0) {
msalInstance
.acquireTokenSilent({
account: accounts[0],
scopes: [
"https://APP_URI/user_impersonation",
],
})
.then((token) => {
console.log("token res is", token);
console.log("access token is", token);
})
.catch((err) => {
console.log("err is", err);
});
Here's the return value from calling acquireTokenSilent:
{
"authority":"https://APP_NAMEb2c.b2clogin.com/APP_NAMEb2c.onmicrosoft.com/b2c_1_flow/",
"uniqueId":"581776f4-6e16-454a-a6ae-ecb49f7f04aa",
"tenantId":"",
"scopes":[
"https://APP_NAMEb2c.onmicrosoft.com/reg_api/user_impersonation"
],
"account":{
"homeAccountId":"581776f4-6e16-454a-a6ae-ecb49f7f04aa-b2c_1_flow.07232d62-7285-4737-97eb-87f0f9b7c38e",
"environment":"APP_NAMEb2c.b2clogin.com",
"tenantId":"",
"username":"testUser#gmail.com",
"localAccountId":"581776f4-6e16-454a-a6ae-ecb49f7f04aa",
"name":"unknown",
"idTokenClaims":{
"exp":1663191498,
"nbf":1663187898,
"ver":"1.0",
"iss":"https://APP_NAMEb2c.b2clogin.com/07232d62-7285-4737-97eb-87f0f9b7c38e/v2.0/",
"sub":"581776f4-6e16-454a-a6ae-ecb49f7f04aa",
"aud":"473fe4d9-260b-46ad-9ad1-f4c4a4f211e6",
"nonce":"65c7ec69-2837-4bdf-b9e3-ae38dbb19c48",
"iat":1663187898,
"auth_time":1663187896,
"name":"unknown",
"emails":[
"testUser#gmail.com"
],
"tfp":"B2C_1_flow",
"at_hash":"qOHPceVj3fEhGGlRq6xh4g"
}
},
"idToken":"TD_TOKEN",
"idTokenClaims":{
"exp":1663191498,
"nbf":1663187898,
"ver":"1.0",
"iss":"https://APP_NAMEb2c.b2clogin.com/07232d62-7285-4737-97eb-87f0f9b7c38e/v2.0/",
"sub":"581776f4-6e16-454a-a6ae-ecb49f7f04aa",
"aud":"473fe4d9-260b-46ad-9ad1-f4c4a4f211e6",
"nonce":"65c7ec69-2837-4bdf-b9e3-ae38dbb19c48",
"iat":1663187898,
"auth_time":1663187896,
"name":"unknown",
"emails":[
"testUser#gmail.com"
],
"tfp":"B2C_1_flow",
"at_hash":"qOHPceVj3fEhGGlRq6xh4g"
},
"accessToken":"ACCESS_TOKEN",
"fromCache":true,
"expiresOn":"2022-09-14T21:38:18.000Z",
"correlationId":"9c71acbb-7ed4-4beb-a282-71ec7d924bd8",
"extExpiresOn":"2022-09-14T21:38:18.000Z",
"familyId":"",
"tokenType":"Bearer",
"state":"",
"cloudGraphHostName":"",
"msGraphHost":"",
"fromNativeBroker":false
}
As you can see, the username property has the emailAddress as it's value and not the actual username.
I've not been able to find concrete guidance on how to get the username. The one resource I found said that UserPrincipleName(UPN) is an optional claim and to add this value in the authToken I should add UPN as an optional claim in the token configuration tab, which is not available in B2C AD. I would love to get some guidance on what I'm doing wrong as getting the username should not be this hard, right ?
Edit 1: I can confirm that the username has been set; in the image below the username is denoted by User Principle Name:
The username property in MSAL's AccountInfo object is populated by the email claim in the ID token. The email claim will be an array, and if there are multiple emails, MSAL will only use the first one as username.
To receive UserPrincipleName (UPN) in the ID token, you'll need to set the user attributes in your B2C tenant. Unfortunately this doesn't seem to be possible with standard user-flows, so you'll need to build a custom policy and sign-in with that instead. See for more: User profile attributes

ServerError: AADSTS50058: A silent sign-in request was sent but none of the currently signed in user(s) match the requested login hint"

SSO fails "ServerError: AADSTS50058: A silent sign-in request was sent but none of the currently signed in user(s) match the requested login hint"
when I use same account for both work and personal azure account.
I have 2 AAD accounts (one is with my work account and the other one is personal account but both attached with same email and both are using same credentials). When I use msal.js library for single sign on application. It takes me to my work account where it asks me to validate the credentials (using standard pop up dialog) by giving full email address and does not authenticate properly even if give right credentials. As I need to login using my personal account
I expect this should validate using my ad alias#company.com credentials. I tried with different account option in the dialog, but it fails and shows up same full email account.
How can I use my adalias#company.com as a default user id?
Here are the piece of the code I am trying to use.
var msalConfig = {
auth: {
clientId: 'xxxxxxxxxx', // This is your client ID
authority: "https://login.microsoftonline.com/{tenantid}" // This is tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
var graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
var requestObj = {scopes: ["user.read", "email"]};
// Is there a way to change here to get the required user id?
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
// Register Callbacks for redirect flow
myMSALObj.handleRedirectCallbacks(acquireTokenRedirectCallBack,
acquireTokenErrorRedirectCallBack);
myMSALObj.handleRedirectCallback(authRedirectCallBack);
function signIn() {
myMSALObj.loginRedirect(requestObj).then(function (loginResponse) {
// Successful login
acquireTokenPopupAndCallMSGraph();
}).catch(function (error) {
// Please check the console for errors
console.log(error);
});
}
Here is the error message I get:
ServerError: AADSTS50058: A silent sign-in request was sent but none of the
currently signed in user(s) match the requested login hint
The expected result is seamless login to other application.
If you want to provide a login_hint to indicate the user you are trying to authenticate try:
var requestObj = {scopes: ["user.read", "email"], loginHint: "adalias#company.com"};
Reference https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q10-how-to-pass-custom-state-parameter-value-in-msaljs-authentication-request-for-example-when-you-want-to-pass-the-page-the-user-is-on-or-custom-info-to-your-redirect-uri

How to get REFRESH_TOKEN_AUTH request to return RefreshToken

I am using Amazon Cognito to login users and save a RefreshToken so they don't have to type their password after the initial setup. I need to be able to login with the RefreshToken and get a new RefreshToken to save for next time. However, when I call InitiateAuthAsync, it does not return the RefreshToken.
C#:
var refreshReq = new InitiateAuthRequest();
refreshReq.ClientId = _clientId;
refreshReq.AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH;
refreshReq.AuthParameters.Add("SECRET_HASH",
SecretHash(_clientId, _clientSecret, username));
refreshReq.AuthParameters.Add("REFRESH_TOKEN", refreshToken);
var clientResp = cognitoProvider.InitiateAuthAsync(refreshReq).Result;
Response:
{
"AuthenticationResult": {
"AccessToken": "<accessToken>",
"ExpiresIn": 3600,
"IdToken": "<idToken>",
"TokenType": "Bearer"
},
"ChallengeParameters": {}
}
And this is the response from the login with a working ResponseToken:
{
"AuthenticationResult": {
"AccessToken": "<accessToken>",
"ExpiresIn": 3600,
"IdToken": "<idToken>",
"RefreshToken": "<refreshToken>",
"TokenType": "Bearer"
},
"ChallengeParameters": {}
}
Apparently this is a bug in the AWS Cognito API. The docs say that InitiateAuth should return an updated RefreshToken, but it does not.
The refresh token is only returned in the initial sign in and it can be used more than once to get a new access token and id token. Be aware of your user pool configuration because it will not work if you are using device tracking and the device use to sign in is not confirmed.
The refresh token is a long-lived token and there's no point returning it as it's still valid for many days. If the default 30-day expiry time is not long enough you can increase it to up to 3650 days.
I agree with the main question here there is an issues if one can't refresh the refresh token.
The best security practice is to regenerate a new Access Token and a new Refresh Token every X minutes. This way if a malicious 3rd party player get a hold on the Access Token / Refresh Token - they will be valid until the next cycle of refreshing the token by the application.
This is very weird that AWS are not supporting such mechanism that is commonly used in the web - such as Passport ID and authentication etc.

Google OAuth flow - passing the resulting tokens via querystring

I'm reviewing some code that uses the OpenIdConnect OAuth flow in asp.net core to auth a user against Google. Once auth is established, there is a handler in OnTicketRecieved that takes the resulting access token and refresh token, and passes that to a different endpoint in the app:
OnTicketReceived = context =>
{
string returnUrl = "/Somewhere/else";
string refreshToken = context.Properties.Items.ContainsKey(".Token.refresh_token") ? context.Properties.Items[".Token.refresh_token"] : "none";
string accessToken = context.Properties.Items.ContainsKey(".Token.access_token") ? context.Properties.Items[".Token.access_token"] : "none";
var email = context.Principal.Claims.Where(c => c.Type == ClaimTypes.Email).Select(c => c.Value).SingleOrDefault();
returnUrl += $"?refreshToken={refreshToken}&accessToken={accessToken}&email={email}";
context.ReturnUri = returnUrl + WebUtility.UrlEncode(returnUrl);
return Task.CompletedTask;
},
My question is - how secure is this? It's effectively leaking refresh tokens that can be seen - although the scope of what you can do with them is very limited, being Google OAuth we're locked down to endpoints we can interact with, and the app secret would also need to be known in order to do anything malicious with these tokens
Thanks
Matt

Node.js - Get my account details from facebook graph api

I'm trying to get my facebook account details in node.js via facebook graph api (graph.facebook.com/me?access_token=token), but I can't get it to work with:
access_token = appid + '|' + appsecret
Here is my code:
var https=require('https');
var access_token = process.argv.slice(2)[0] + '|' + process.argv.slice(2)[1];
var options = {
host: 'graph.facebook.com',
path: '/me?access_token=' + access_token
};
https.get(options,function(res){
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
console.log(data);
});
});
And the error I'm getting:
{"error":{"message":"An active access token must be used to query
information about the current
user.","type":"OAuthException","code":2500}}
Edit:
After reading document again: https://developers.facebook.com/docs/facebook-login/access-tokens.
I realize facebook has 4 types of token:
User Access Token
App Access Token = app_id|app_secret
Page Access Token
Client Token
App Access Token is used to read the app settings (not the user info).
And "https://graph.facebook.com/me" is for user info so we need to use User Access Token
example of using app access token:
https://graph.facebook.com/APP_ID/roles?&access_token=APP_ID|APP_SECRET
I was missing the &grant_type=client_credentials in the url:
To Get the app Access Token for facebook api.
But the app token isn't the one with which you get information about your user, in this case I need the user access token which I can get from Graph Api Explorer

Resources