How to enforce AAD Application Role authorization with Azure Functions? - oauth-2.0

This page describes how to add Application app roles to an application in Azure Active Directory using the manifest.
Code sample from the page:
"appId": "8763f1c4-f988-489c-a51e-158e9ef97d6a",
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "ConsumerApps",
"id": "47fbb575-859a-4941-89c9-0f7a6c30beac",
"isEnabled": true,
"description": "Consumer apps have access to the consumer data.",
"value": "Consumer"
}
],
"availableToOtherTenants": false,
When calling an an Azure Function from an application authenticated using the client_credentials grant type, how do you enforce it to belong to the application role?
I've Googled but been unable to find clear documentation that explains how this authorization is done for Azure Functions.
My Test Function App
I've created a simple "hello <name>" Azure Function from within the Azure Portal which I call from Postman.
#r "Microsoft.Azure.WebJobs.Extensions.Http"
#r "Newtonsoft.Json"
using System.Net;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
public static IActionResult Run(HttpRequest req, ILogger log, ClaimsPrincipal claimsPrincipal)
{
var name = req.Query["name"];
log.LogInformation($"C# HTTP trigger function processed a request: {name}");
var cp = new {
Identity = new {
claimsPrincipal.Identity.AuthenticationType,
claimsPrincipal.Identity.IsAuthenticated,
claimsPrincipal.Identity.Name
},
Claims = claimsPrincipal.Claims.Select(claim => new
{
claim.Type,
claim.Value
})
};
log.LogInformation($"ClaimsPrincipal ({claimsPrincipal.GetType().FullName}): {JsonConvert.SerializeObject(cp, Formatting.Indented)}");
return (IActionResult)new OkObjectResult($"Hello, {name}");
}
Firstly I authenticate using https://login.microsoftonline.com/<Tenant ID>/oauth2/v2.0/token and capture the access_token.
Request Body Example:
grant_type:client_credentials
client_id:<Application ID>
client_secret:<Client Secret>
scope:https://<Function-app-name>.azurewebsites.net/.default
Example Result:
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAi......"
}
Then I call my Azure Function using https://<function-app-name>.azurewebsites.net/api/hello?name=World and a header containing Authorization: Bearer eyJ0eXAi.......
Authentication works fine, as does calling the Azure Function. However, I can add a new Application via App registrations in the Azure Portal, authenticate and then call the Azure Function freely. I don't know how to restrict access the the Azure Function to only Applications that have a specific application role.

I don't know how to restrict access the Azure Function to only Applications that have a specific application role.
If you just want the App who has the ConsumerApps permission access your function, follow the steps below.
1.Navigate to the AD App of your function in the Azure Active Directory in the portal -> click the Managed application in local directory -> Properties -> set the User assignment required to Yes.
2.Then you could try to get the token with your AD App again, you will find the app could not get the token successfully, you will get the error like below, because your client app does not have the ConsumerApps permission.
3.To access the function successfully, we just need to add the Application permission for the Client AD App you used.
Navigate to the client AD App in the portal -> API permissions -> Add a permission -> click APIs my organization uses -> search for your function AD App name -> click the app -> Application permissions -> add the Consumer permission -> click the Grant admin consent for xxx button.
Wait for a while, then try to get the token again, it works fine.
Use the token to call function, also works.

Related

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3, but can't fetch token using (client_credentials) grant type

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3
npm install #aws-sdk/client-cognito-identity-provider.
The following code shows how I created the resources server and the user pool client, using the mentioned👆 SDK...
let poolName = 'UserPool';
const client =new CognitoIdentityProviderClient({
region: process.env.COGNITO_AWS_REGION
});
// create resource server
const createResourceServerCommand = new CreateResourceServerCommand({
Name: poolName,
UserPoolId: UserPool.Id,
Identifier: 'https://localhost:8080/api/v2',
Scopes: [
{
ScopeName: 'access',
ScopeDescription: 'General access to API'
}
]
});
const { ResourceServer } = await client.send(createResourceServerCommand);
// create the user pool client
const createUserPoolClientCommand = new CreateUserPoolClientCommand({
ClientName: 'Default',
UserPoolId: UserPool.Id,
ExplicitAuthFlows: ['USER_PASSWORD_AUTH'],
GenerateSecret: true,
AllowedOAuthFlows: ['client_credentials'],
SupportedIdentityProviders: ['COGNITO'],
AllowedOAuthScopes: [ 'https://localhost:8080/api/v2/access' ]
});
const { UserPoolClient } = await client.send(createUserPoolClientCommand);
...but, I can't fetch tokens using the grant type client_credentials. Therefore getting the following error.
{
"error": "invalid_grant"
}
However, if I use AWS console to navigate to the user pool > Client > Edit the hosted UI and click on the save button without making any changes...
... I am able to fetch a token using the client_credentials grant type.
Is there any setting that I might be missing in the above code that AWS console is setting? I need the following code to automate the creation of user pools.
When I switched to the old I noticed this notification
Apparently, Oauth flows are not enabled by default. Hence adding the following attribute to the CreateUserPoolClientCommandInput object AllowedOAuthFlowsUserPoolClient: true enables it. Hope this helps some newbie like me out there.

Issue authenticating to Auth0 via Swagger UI (.NET) with client credentials

I'm trying to authenticate to my Auth0 tenant via the Swagger UI (auto-generated by Swashbuckle.AspNetCore) using client credentials.
I'm getting the following error:
Auth ErrorError, error: access_denied, description: Non-global clients
are not allowed access to APIv1
Here's a screen shot:
Open API spec looks like this:
"securitySchemes": {
"auth0": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://example.auth0.com/oauth/token"
},
"authorizationCode": {
"authorizationUrl": "https://example.auth0.com/authorize?audience=test",
"tokenUrl": "https://example.auth0.com/oauth/token",
"scopes": { }
}
}
}
}
I suspect this has something to do with the audience not being specified. I was having a similar issue with authorization code flow, but manged to get this working by appending the audience as a query-string param to the authorizationUrl (as shown above). Unfortunately the same trick doesn't work with client credential flow (i.e. attempting to append the audience to the tokenUrl). I need to support client credential flow, as one or more of the routes need to be locked down to M2M tokens only.
Interestingly, it works if I use the global client ID / secret (found under the advanced settings of the tenant), but I'm not sure if we should use this...
Anyone else ran into this issue and if so, any luck with finding a solution?

How to change msal authority in runtime?

Background:
I have an angular 7 app.
The app authenticates using Azure Active Directory B2C and Msal for angular
https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-angular
I have created 2 user flow's in AAD B2C:
Signin flow V2
Signup flow V2
both have UI customized,
both contain a redirect button to the other AAD B2C flow.
(from register -> login | from login -> register)
I have setup MSAL module in angular, with Signin flow as the default authority
e.g
MsalModule.forRoot({
...,
authority: <"https://mytenant.b2clogin.com/tfp/mytenant.onmicrosoft.com/B2C_1_Signin">,
...
})
(notice B2C_1_Signin flow name)
This is working well, user can login and register (navigate between flows from within a flow and authenticate).
Problem:
The problem start when I try to manually change the MSAL module authority
(that was defined in MSAL init as B2C_1_Signin flow when app starts).
Example:
User get to a welcome page and can click Login / Register.
If the user clicks login,all is well because the authority link was defined at start as Signin flow (B2C_1_Signin).
If the user would like to register,
I need to change the authority link
to the selected AAD flow (B2C_1_Signup) and then I call MSAL loginRedirect(), user get redirects to right AAD flow, enter his details and redirect back to the app.
Then the app will start a new login flow and redirects to the login flow as if the user is not authenticated.
This only happens when I change the authority link manually (I have no other choice in order to navigate to right flow).
Otherwise, all is working as expected.
How should I change the authority, so the app will not fail the user authentication?
I have tried using default AAD Signup v1 flow with no customization.
I have tried to reveres the process in order to isolate the problem (setup Signup flow as default,
change authority manually to Signin flow, same error (as expected).
I have searched many different sites and forums but none have the same issue or a fix.
https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/498
https://medium.com/#sambowenhughes/configuring-your-angular-6-application-to-use-microsoft-b2c-authentication-99a9ff1403b3
and many more...
MSAL for angular setup:
"authentication.module.ts"
MsalModule.forRoot({
clientID: "<my-client-id>",
authority: "https://tenant.b2clogin.com/tfp/tenant.onmicrosoft.com/B2C_1_Signin",
redirectUri: "http://localhost:4200/dashboard",
/* default is true */
validateAuthority: false,
/* Defaults is 'sessionStorage' */
cacheLocation : "localStorage",
/* Defaults is 'redirectUri */
postLogoutRedirectUri: "http://localhost:4200/",
/* Ability to turn off default navigation to start page after login. Default is true. */
navigateToLoginRequestUrl : false,
/* Show login popup or redirect. Default:Redirect */
popUp: false,
})
AuthService wrapping MSAL auth service:
"auth.service.ts"
constructor(private msalService: MsalService) {}
public login(scopes: string[] = null, queryParams: string = null): void {
this.msalService.authority = this.tenantConfig.baseURL + "/" + this.tenantConfig.tenant + "/" + this.tenantConfig.signInPolicy;
this.msalService.loginRedirect(scopes, queryParams);
}
public signup(scopes: string[] = null, queryParams: string = null): void {
this.msalService.authority = this.tenantConfig.baseURL + "/" + this.tenantConfig.tenant + "/" + this.tenantConfig.signUpPolicy;
this.msalService.loginRedirect(scopes, queryParams);
}
I expect the Msal module to work as usual even if I change the initial defined authority.
Have you tried submitting an issue against the MSAL GitHub library? The issues section for MSAL.JS can be found here : https://github.com/AzureAD/microsoft-authentication-library-for-js/issues
There's actually an ongoing issue on trying to change authority during runtime per the issue here : https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/784
As it's not exactly the same, I suggest filing a new github issue. However it looks like this is not a supported feature as of yet.
We had a similar problem with our application. The fix was essentially turning off MFA.
If MFA is turned on for sign-in, switching from sign-up to sign-in wasn't giving us an MFA prompt and unceremoniously did not authorize the user to be signed in.
Our solution was to detect if the sign in was attempting directly after a sign up and have a non-MFA sign in flow for that case specifically. Not sure if you're doing MFA, but if you are it's worth a look.

Identity Server 4 Authorization Code Flow with Client Credentials (allowing one client instance deny another)

What I got so far:
In a project I have an authorization server (Identity Server 4), some (let's say two) protected APIs (Api Resource) and some trusted clients (automated, no user interaction) which should access the Identity Server via the backchannel (right?). Imagine the client is a Amazon Fire TV box kind thingy.
According to what I have read so far over the last weeks a suitable flow for this scenario is the OpenID Connect Authorization Code Flow.
clients are trusted (and can maintain a secret)
Authorization Code flow supports refresh tokens (which I want to use)
the client is actually not the resource owner but requires access to the full api resource
What I have in my (theoretical) structure:
I have two API Resources (one resource for each API version)
api.v1
api.v2
I also have two series of my API clients
client.v1 supports only api v1 & should only have access to api.v1 resource
client.v2 supports api v1 & v2 and therefore should have access to both api resources
Identity Server 4 StartUp.cs configuration (so far)
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources
(
new List<ApiResource>
{
new ApiResource("api.v1", "API v1"),
new ApiResource("api.v2", "API v2")
}
)
.AddInMemoryClients
(
new List<Client>
{
new Client
{
ClientId = "client.v1",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret1".Sha256()) },
AllowedScopes = { "api.v1" }
},
new Client
{
ClientId = "client.v2",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowAccessTokensViaBrowser = false,
ClientSecrets = { new Secret("secret2".Sha256()) },
AllowedScopes = { "api.v1", "api.v2" }
}
}
);
}
The theory what I am struggling with is the authorization code part.
I want to have each client instance (again imagine it as a small box) a different authorization code allowing one instance access but deny fo another one.
Is the authorization code intended to be used for that?
And one important thing I haven't understood in all the time: CodeAndClientCredentials defines two grant types. Does this mean connecting with that requires both (code AND client credentials) or is it an one of them definition (code OR client credentials).
The Identity Server 4 code I am struggling with is:
In the code defining the client I can only find AuthorizationCodeLifetime but no field to set the authorization code itself.
It seems I can define a list of client secrets.
ClientSecrets = { new Secret("secret1".Sha256()) },
Does this mean one client Id can have multiple secrets used? Are different client secrets better suitable for my "allow one deny the other" problem?
Edit
Ok, I have re-read that and now I got it (at least a bit more): the authorization code is not defined sent by the client but the client receives it.
The authorization code flow returns an authorization code (like it says on the tin) that can then be exchanged for an identity token and/or access token. This requires client authentication using a client id and secret to retrieve the tokens from the back end
from this blog here
But how would I have to configure my Identity Server to allow one instance and deny another.
By using different client secrets? Using extension grants?

Gmail API returns 403 error code and "Delegation denied for <user email>"

Gmail API fails for one domain when retrieving messages with this error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 OK
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Delegation denied for <user email>",
"reason" : "forbidden"
} ],
"message" : "Delegation denied for <user email>"
}
I am using OAuth 2.0 and Google Apps Domain-Wide delegation of authority to access the user data. The domain has granted data access rights to the application.
Seems like best thing to do is to just always have userId="me" in your requests. That tells the API to just use the authenticated user's mailbox--no need to rely on email addresses.
I had the same issue before, the solution is super tricky, you need to impersonate the person you need to access gmail content first, then use userId='me' to run the query. It works for me.
here is some sample code:
users = # coming from directory service
for user in users:
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
####IMPORTANT######
credentials_delegated = credentials.with_subject(user['primaryEmail'])
gmail_service = build('gmail', 'v1', credentials=credentials_delegated)
results = gmail_service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
for label in labels:
print(label['name'])
Our users had migrated into a domain and their account had aliases attached to it. We needed to default the SendAs address to one of the imported aliases and want a way to automate it. The Gmail API looked like the solution, but our privileged user with roles to make changes to the accounts was not working - we kept seeing the "Delegation denied for " 403 error.
Here is a PHP example of how we were able to list their SendAs settings.
<?PHP
//
// Description:
// List the user's SendAs addresses.
//
// Documentation:
// https://developers.google.com/gmail/api/v1/reference/users/settings/sendAs
// https://developers.google.com/gmail/api/v1/reference/users/settings/sendAs/list
//
// Local Path:
// /path/to/api/vendor/google/apiclient-services/src/Google/Service/Gmail.php
// /path/to/api/vendor/google/apiclient-services/src/Google/Service/Gmail/Resource/UsersSettingsSendAs.php
//
// Version:
// Google_Client::LIBVER == 2.1.1
//
require_once $API_PATH . '/path/to/google-api-php-client/vendor/autoload.php';
date_default_timezone_set('America/Los_Angeles');
// this is the service account json file used to make api calls within our domain
$serviceAccount = '/path/to/service-account-with-domain-wide-delagation.json';
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $serviceAccount );
$userKey = 'someuser#my.domain';
// In the Admin Directory API, we may do things like create accounts with
// an account having roles to make changes. With the Gmail API, we cannot
// use those accounts to make changes. Instead, we impersonate
// the user to manage their account.
$impersonateUser = $userKey;
// these are the scope(s) used.
define('SCOPES', implode(' ', array( Google_Service_Gmail::GMAIL_SETTINGS_BASIC ) ) );
$client = new Google_Client();
$client->useApplicationDefaultCredentials(); // loads whats in that json service account file.
$client->setScopes(SCOPES); // adds the scopes
$client->setSubject($impersonateUser); // account authorized to perform operation
$gmailObj = new Google_Service_Gmail($client);
$res = $gmailObj->users_settings_sendAs->listUsersSettingsSendAs($userKey);
print_r($res);
?>
I wanted to access the emails of fresh email id/account but what happened was, the recently created folder with '.credentials' containing a JSON was associated with the previous email id/account which I tried earlier. The access token and other parameters present in JSON are not associated with new email id/account. So, in order make it run you just have to delete the '.credentails' folder and run the program again. Now, the program opens the browser and asks you to give permissions.
To delete the folder containing files in python
import shutil
shutil.rmtree("path of the folder to be deleted")
you may add this at the end of the program
Recently I started exploring Gmail API and I am following the same approach as Guo mentioned. However, it is going to take of time and too many calls when we the number of users or more. After domain wide delegation my expectation was admin id will be able to access the delegated inboxes, but seems like we need to create service for each user.

Resources