I have an application let's name it 'Apple' which is registered with Azure AD having delegated rights on Azure Management API application. When requested to this application it creates azure resource ex. storage account automatically and this works fine.
I have another application which is MVC application and it's also registered with same AD tenant. The second application uses following code for retrieving access token:
var clientCredentials = new ClientCredential(ConfigurationManager.AppSettings["AD_ClientID"], ConfigurationManager.AppSettings["AD_Client_AccessKey"]);
var authContext = new AuthenticationContext(string.Format(ConfigurationManager.AppSettings["AD_Tenant_Login_Url"], ConfigurationManager.AppSettings["AD_Tenant_Id"]));
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["AD_Resource"], clientCredentials);
if (result == null)
{
throw new InvalidOperationException("Could not get the token");
}
return result.Result;
The result is an access token having different properties. Now second application, retrives access token with access to resource apple, which it then passes to Apple application in authorization header.
Authorization:bearer TokenString
The Apple application is having Authorize attribute added to controller.
The application is configured with Owin with oauth application with following code
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
Please note that the access token is retrieved from second application using its own AppId and Secret key; whereas the other(Apple) application uses its own AppId and secret key for validating the token.
So my problem is, the APPLE application always returns 401 not authorize code
To above, question, the answer was, Resource ID (during token request) and Audience Id (during validation of token in second application) were not matching. Keeping those same solved the problem.
Then I ran into another issue, which I have described here
It seems, If I work with newer Azure Portal (which is still in preview version), the AD token does not include "Roles" field in JWT token. If I follow same procedure in Older Portal for configuring apps, then AD includes "Roles" field in JWT token and scenario executes as expected.
I should avoid using Azure new portal for preview features at least!
Related
TL;DR
How do I retrieve the access_token from an Identity Google external login from (say) a controller?
Full details:
I'm working on a .net6 website with Identity and Google external login.
I have added some Google API (Drive and Youtube) functionality which worked fine locally using GoogleWebAuthorizationBroker.AuthorizeAsync to get the credentials to instantiate the services for Drive and Youtube.
When deployed to the server, I got errors due to the fact that the server doesn't support GoogleWebAuthorizationBroker.AuthorizeAsync, and found out I need to use OAuth2 as described here: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
The problem is that I am already using Identity, so cannot follow that documentation by the letter (I cannot get an instance of IGoogleAuthProvider injected in my classes as I would if I had .AddGoogleOpenIdConnect in my services)
I have tested that initializing the Drive and Youtube services with the access token I get from the Identity Google external logins works fine:
var token = "<ACCESS_TOKEN_VALUE>"
var credential = GoogleCredential.FromAccessToken(token);
_driveService = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "MyWebApp"
});
The problem is that being a website, I would have to store the refresh tokens and access tokens for all users somewhere (i.e. refresh token to db, and access token to session or in the Identity.Claims)
In my .AddGoogle() call I have the option googleOptions.SaveTokens = true; enabled, but I do not know how I can retrieve this tokens from.
Is there a proper way of storing/retrieving that token?
Haiya all! I have the following code that sets up my Google API:
// Open the FileStream to the related file.
using FileStream stream = new("Credentials.json", FileMode.Open, FileAccess.Read);
// The file token.json stores the user's access and refresh tokens, and is created
// automatically when the authorization flow completes for the first time.
UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(stream).Secrets,
new[] { SheetsService.Scope.Spreadsheets, YouTubeService.Scope.YoutubeReadonly },
"admin",
CancellationToken.None,
new FileDataStore("Token", true),
new PromptCodeReceiver()
);
// Create Google Sheets API service.
builder.Services.AddSingleton(new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = name,
}));
// Create Youtube API service.
builder.Services.AddSingleton(new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = name,
}));
With this code, all my services are injected properly into the service provider I'm using. However, after 7 days of being up the ASP.Net website seems to not refresh the token, even though my token file has a refresh token. This is, as I would imagine, due to the fact OAuth tokens only last 7 days.
So, how would I get Google's API to automatically refresh my token? As this is a website, it needs to be online for long amounts of time without going down due to an expired token.
If your app is still in testing refresh tokens expire after 7 days. To have them expire longer you will need to set your app into production, and possibly verify it.
Refresh token expiration
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
web app vs installed app
I'm surprised that your code is working hosted on a website.
GoogleWebAuthorizationBroker.AuthorizeAsync is intended for installed applications. Its not going to work for Asp .net hosted on a web site. The reson it wont work is that it causes the consent browser to open on the machine the code is running on. This will work in development but as soon as you try to host it on a webserver its not going to work as the server cant spawn a local web browser.
You should be following Web applications (ASP.NET Core 3)
Which will use dependency injection
public void ConfigureServices(IServiceCollection services)
{
...
// This configures Google.Apis.Auth.AspNetCore3 for use in this app.
services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = {YOUR_CLIENT_ID};
options.ClientSecret = {YOUR_CLIENT_SECRET};
});
}
You dont need to worry about refreshing your access token the library will handle that for you.
Is the idea to let the ASP.NET app access Youtube and Sheets...
on behalf of the end user or
on behalf of itself (no end user involved?
In case of (1), using GoogleWebAuthorizationBroker to start an OAuth authorization flow is the right way to go, but you probably shouldn't store the refresh token permanently. Either store it for the duration of the end user session only, or don't store it at all and trigger a new OAuth authorization flow every time the access token expires.
In case of (2), you should use a service account and load credentials by using GoogleCredentials.GetApplicationDefaultAsync(). If the app runs on Google Cloud, attach a service account to the underlying compute resource; if it's running elsewhere you can use Workload identity federation or service account keys.
I'm implementing Azure AD B2C in a new ASP.NET Core 2.1 app.
I've already created the Azure AD B2C tenant and registered my app, etc.
After I login, I get redirected to the URL I specify and I see the token in the URL but I get an error stating the app requires authentication -- see below:
I saw a few similar posts and what I gather is that the token is automatically validated by the middleware. Is that not so?
What do I need to do at this point?
The code I included in my app are as follows:
In ConfigureServices() method:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
In Configure() method in Startup.cs, all I have is app.UseAuthentication();
In my controller, I'm also using [Authorize] to make sure my actions are not open to anonymous users.
What am I missing? How do validate the token and get the claims?
Combining our discussion here as an answer.
The typical approach to this kind of app is that you allow unauthenticated clients to download the HTML, JS, and other static content.
Then the front-end can use MSAL.JS to authenticate the user.
The front-end SPA will get an Id token which tells the front-end who the user is.
MSAL.JS also allows you to get access tokens to call APIs.
It uses hidden iframes + the Implicit Grant flow to do this.
That access token will then need to be attached to requests to the API as a header (Authorization: Bearer token-goes-here).
MSAL.JS will use session or local storage to store the tokens (this is configurable).
So no cookies are used in this setup.
Then the back-end API should authenticate the access token it receives in the header.
What you have there is already sufficient to authenticate the token.
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
The JWT Bearer authentication handler will load the OpenId Connect metadata document on startup from the authority configured here.
That allows it to get the B2C tenant's public signing keys among other things.
This info allows the handler to then validate access tokens as they come in without interacting with B2C in any way.
It checks the signature is valid, the issuer is valid, and that the audience in the token is what has been configured.
Authorization is of course not handled by the authentication handler, so you must then also check that the calling user actually is allowed to access the resource they are accessing.
The user id is available in the access token.
MSAL.JS may have done some validation on the token as well (I can't remember right now if it did), but doing validation in the front-end is something that can be worked around by anyone with control of the user's browser.
Validation on the API side is the most important piece.
I obtain an access_token and id_token from AzureAD for my app, which is using OAuth2 with the implicit flow. This is a sample URL where I obtain the tokens:
https://login.microsoftonline.com/my_tenant_id/oauth2/v2.0/authorize?response_type=id_token+token&client_id=my_client_id&state=some_state&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fsign-in&scope=openid%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&nonce=some_nonce
The scope is openid https://grap.microsoft.com/user.read.
The response_type is id_token+token.
I also have a Asp.Net backend, I want to secure. So I use the Authorize Attribute for my controller and send a token in the header as like this: "Authentication : Bearer THE_TOKEN".
My configuration in Startup.cs looks like this:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = string.Format("https://login.microsoftonline.com/{0}/v2.0/",
"d67853c3-db96-4dac-a37b-f2bfb12b42d1"),
Audience = "8422b3fb-5612-4fdd-a90f-707d7218de57"
});
From what I have read, the access token should be used for this, and the id_token should not leave the frontend. But authentication in the backend only works with the id token in my case. The access_token can not be signed Bearer error="invalid_token", error_description="The signature is invalid".
Looking at the access_token in jwt.io, I see the tokens have different audiences and issuers. The access_token for example has this
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/d67853c3-db96-4dac-a37b-f2bfb12b42d1/",
whereas the id token has this
"aud": "my_client_id",
"iss": "https://login.microsoftonline.com/my_tenant_id/v2.0",
So it seems to me, the access_token was somehow issued for the Graph API. Would be glad if someone could tell me, what i am doing wrong or how I can try to solve my issues.
edit:
It WAS working as intended before when I used the scope openid profile. But due to changes in Azure, this scope is not valid anymore and Microsoft directed me to use the scope mentioned above.
As you mentioned, the access token you requested is for the Microsoft Graph. And the id_token is only for the client to authenticate the user instead of for the resource server.
To protect the web API using the Azure AD V2.0 endpoint, we can acquire the access token for the web API like request below:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=token&client_id={client_id}&scope=api://{client_id}/access_as_user&redirect_uri={redirect_uri}
And here is the code using protecting the web API via Azure AD V2.0 endpoint:
public void ConfigureAuth(IAppBuilder app)
{
System.Diagnostics.Trace.TraceWarning("Hello");
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = clientId,
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}
}
More detail about protecting the web API via Azure AD V2.0 endpoint, you can refer the document below:
Calling a web API from a .NET web app
I'm trying to get a refresh token set up in my Xamarin.Forms app using AAD B2C. I've got everything set up but run into issues when calling LoginAsync on my MobileServiceClient. All of the docs and examples I can find show to update my LoginAsync method to this:
var user = await App.MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
new Dictionary<string, string>() { { "response_type", "code id_token" } });
Except that the MobileServiceClient does not take a Dictionary<string, string> for the second parameter. It takes a JObject. Here's what my current code looks like:
var authResult = await App.AuthenticationClient.AcquireTokenAsync(Constants.Scopes, "", UiOptions.SelectAccount, string.Empty, null, Constants.Authority, Constants.Policy);
var payload = new JObject();
payload["access_token"] = authResult.Token;
var user = await App.MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
I can not find an example use the JObject anywhere.
It is as simple as adding payload["response_type"] = "code id_token"; to my payload?
AFAIK, Mobile Apps support two authentication flows (client-managed flow and server-managed flow).
Client-managed authentication
Your app can independently contact the identity provider and then provide the returned token during login with your backend. This client flow enables you to provide a single sign-on experience for users or to retrieve additional user data from the identity provider.
After you retrieved the token, then you would login with your azure mobile backend by passing the token into a JObject instance as follows:
JObject payload = new JObject();
payload["access_token"] = ar.AccessToken;
var user = await client.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
For more details about other identity providers via client-flow authentication, you could refer to Client-managed authentication.
Server-managed authentication
Your app directly contacts your mobile backend, then your azure mobile backend contacts the identity provider and provide you with the logged user.
For Xamarin.Forms UWP app, you could login as follows:
For Xamarin.Forms IOS app, you could login as follows:
For more details about server-managed authentication in Xamarin.Forms, you could refer to Add authentication to your Xamarin Forms app.
UPDATE:
I have checked that if you call MobileServiceClient.LoginAsync in PCL, you could not see any extensions for LoginAsync. As you could see, there are many extension LoginAsync methods in the Microsoft.WindowsAzure.Mobile.Ext.dll for each platform. You need to define the IAuthenticate interface and implement it in each of your app (uwp, android, ios, etc.), for more details you could refer to here.