I've setup an Azure Mobile App Service backend and there is a Xamarin app consuming it's services. It uses custom authentication in the Azure Mobile App Service for registering and authenticating the users of the app.
For local development/debugging application's OWIN startup class contains some code to setup the authentication options of the app service, as described in https://azure.microsoft.com/nl-nl/documentation/articles/app-service-mobile-dotnet-backend-how-to-use-server-sdk/#local-debug.
In Azure the Mobile App Service's authentication is enabled (Authentication / Authorization) setting the 'Action to take when request is not authenticated' option to 'Allow request (no action)' so the application handles the request authentication.
This all works as desired.
Now we would like to support a custom domain on our Mobile App Service and support the current ourmobileappservice.azurewebsites.net domain. We've configured the custom domain, configured it's SSL certificate and all works well. New tokens are issued with the custom domain as audience/issuer and it's also validated in this manor.
But when issuing a token with ourmobileappservice.azurewebsites.net as audience/issuer, it's rejected during token validation. It seems only our custom domain is allowed as valid audience.
For local development we're specifying the app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { ... }), also setting the ValidAudiences property. So I wanted to use this setup for the Azure environment as well, so we can specify multiple valid audiences for token validation. Note: of course the AppServiceAuthenticationOptions is different than for local development, e.g. SigningKey comes from Azure's environment variables).
Unfortunately Azure doesn't seem to use this at all. It still keeps failing in the exact same way. As you can see only the custom domain is specified as valid audience:
Warning JWT validation failed: IDX10214: Audience validation
failed. Audiences: 'https://ourmobileappservice.azurewebsites.net/'.
Did not match: validationParameters.ValidAudience:
'https://ourcustom.domain.com/' or
validationParameters.ValidAudiences: 'null'.
How can I configure the Azure Mobile App Service with custom authentication setup so it's valid audiences supports both the custom domain as the previous ourmobileappservice.azurewebsites.net?
Edit
The valid audiences are specified for Azure as follows:
public static void ConfigureMobileApp(IAppBuilder app)
{
...
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
ValidIssuers = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
TokenHandler = config.GetAppServiceTokenHandler()
});
...
}
You can sign the token with any URL which you like in your custom auth provider. Whatever you specify in the AppServiceLoginHandler.CreateToken() method will go into the JWT.
When it comes to validation of the token, if you're debugging locally the list of URLs specified in the middleware will be used to validate the audience and issuer. In production Azure will magically use your default Azure domain and custom domains.
The way I did it was to create a new web.config AppSetting which contains the valid signing URLs for all environments.
<add key="ValidUrls" value="https://api.myproductiondomain.com/, https://myproductionapp.azurewebsites.net/, http://localhost:59475/" />
In the Startup.MobillApp.cs I'm populating the valid audiences and issuers from this list.
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = validUrls,
ValidIssuers = validUrls,
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Now in my login method before generating the token, I'm checking that the hostname of the current request is in the same AppSetting whitelist. If it's valid use the current hostname as the Audience and Issuer for my token.
Something like this;
// Get current URL
var signingUrl = $"{this.Request.RequestUri.Scheme}://{this.Request.RequestUri.Authority}/";
// Get list from AppSetting
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
// Ensure current url is in whitelist
if (!validUrls.Contains(signingUrl))
{
return this.Request.CreateUnauthorizedResponse();
}
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
};
var signingKey = this.GetSigningKey();
// Sign token with this
var audience = signingUrl;
var issuer = signingUrl;
// Set expirey
var expiry = TimeSpan.FromHours(72);
// Generate token
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims,
signingKey,
audience,
issuer,
expiry
);
We actually just made a set of updates which allow you to set audiences in the portal. If you return to the AAD settings under App Service Authentication / Authorization, you should see some new options under the "Advanced" tab. This includes an editable list of allowed token audiences.
If you add https://ourmobileappservice.azurewebsites.net to that list, you should be good to go.
Related
I’m trying to create an .NET console application to authenticate users for an Azure function app. I want to authenticate users using their AD credentials and then create a token based on that. I believe I need to enable “Public client flows” in order to achieve this. I’m pretty new to this, but after many failed attempts I managed to get it working after setting the Application ID URI and the scope in the “Expose an API” section. I believe the manifest refers to this property as “identifierUris”. An according to some findings both public access and identifierUris cannot be used at the same time.
Is there an alternative way to achieve this? Any explanations or reasonings as to why this is not ideal would be appreciated as well.
This is the code we're using to retrieve the token and use it:
var publicClient = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(authorityUri)
.WithRedirectUri(redirectUri)
.Build();
var accessTokenRequest = publicClient.AcquireTokenInteractive(scopes);
var accessToken = await accessTokenRequest.ExecuteAsync();
restRequest.AddHeader("authorization", "Bearer " + token);
This is the Pulumi code creating the Azure AD Application, where functionApp is the Pulumi.Azure.AppService.FunctionApp that we are trying to authorize against:
var azureApp = new AzureAD.Application(name, new AzureAD.ApplicationArgs
{
DisplayName = name,
AvailableToOtherTenants = false,
Homepage = "https://VisualStudio/SPN",
Oauth2AllowImplicitFlow = true,
ReplyUrls = { "http://localhost" },
IdentifierUris =
{
functionApp.DefaultHostname.Apply(dnsName => "https://" + dnsName)
},
PublicClient = true
}, new CustomResourceOptions {DependsOn = functionApp});
When PublicClient is set to false, this deploys fine. When it is set to true, the the underlying API call returns a 400 error with this text:
Property identifierUris is invalid
So we set PublicClient to false and then manually update it to true in the portal, which works fine:
What are we missing?
You don't need to set PublicClient to true because it applies to ROPC flow, Device Code Flow or Windows Integrated Auth flow as your screenshot shows.
But according to your code, I think you are not using any the three auth flows.
You should create a new app registration which represents the API/server side (your Azure function app) and do "Expose an API".
Add the scope/permission (exposed by the API/server side) in your app registration which represents the client side (.NET console application).
To use PublicClientApplicationBuilder, you just need to modify "allowPublicClient": true in the manifest file of the app registration of the client side. Don't add any IdentifierUris or set the Application ID URI and the scope in the "Expose an API" section because this should be done in the app registration which represents the API/server side.
I'm trying to secure my aspnet core web API server by making it authenticate against Azure B2C using user-provided JWT bearer tokens. I've followed some sample code found on official microsoft github pages, but can't seem to get it working.
In my B2C policy, I've got it set to use the default issuer URL format: https:////v2.0/
In my web application, I've got that same URL specified as the Authority in the JWT options.
When I submit an HTTP request to my server, the identity server code fails as it tries to reach out to B2C to fetch the openid-configuration. It fails with the following error ...
HttpRequestException: Response status code does not indicate success: 404 (Not Found).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
IOException: IDX20804: Unable to retrieve document from: 'https://innovativelitfoundry.b2clogin.com/0f55bfb6-6af5-4293-8963-29ae099183cc/v2.0/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(string address, CancellationToken cancel)
InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://innovativelitfoundry.b2clogin.com/0f55bfb6-6af5-4293-8963-29ae099183cc/v2.0/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.GetConfigurationAsync(CancellationToken cancel)
Indeed, that URL will not work because it does not appear to be including the policy name, from the used token, in the query string. So, that URL does indeed not work.
I'm unsure how to make the code provide that policy name in the query string, though? Or should it be doing that automatically?
Here is the code, in my aspnet core web api application, where I configure the authentication settings ...
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services
.AddAuthentication(ConfigureAuthentication)
.AddJwtBearer(ConfigureJwt);
services
.AddCors();
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services
.AddSingleton(Configuration);
}
private void ConfigureAuthentication(AuthenticationOptions options)
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}
private void ConfigureJwt(JwtBearerOptions options)
{
var tenant = Configuration["AzureAd:TenantId"];
options.Audience = Configuration["AzureAd:ApplicationId"];
options.Authority = $"https://innovativelitfoundry.b2clogin.com/{tenant}/v2.0/";
}
Does anybody perhaps know what I may be doing incorrectly here? How can I get my aspnet core web api application to correctly pull down that openid configuration document?
You must set options.Authority to an authority URL that includes the policy ID:
options.Authority = $"https://innovativelitfoundry.b2clogin.com/{tenant}/{policy}/v2.0/";
As long as you have set the issuer claim for all policies to the issuer URL that doesn't contain the policy ID, then your API application can download the configuration document for any policy and then validate tokens that are issued for all policies.
I've almost configured my OpenId owin authentication/authorization in Azure Active Directory. My configuration is the following:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "AppServiceAuthSession"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = _authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
RedirectUri = PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(context.AuthenticationTicket.Identity.Claims);
var appToken = "MyToken";
id.AddClaim(new Claim("MyTokenKey", appToken));
context.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties
);
}
},
});
But I want to add one more application token (not user token) to claims list to be able to have ability to use this token in any place on my site. Also it's good point for me that I don't need to get this token from my external token provider more then one time per an authentication session.
But place, where I'm going to add my logic (AuthorizationCodeReceived as well as other methods from OpenIdConnectAuthenticationNotifications) is called only when I use my local IIS(run locally), when I try to use azure IIS, this method has not been called at all. In this case my User is authenticated anyway, but this method and the similar methods from OpenIdConnectAuthenticationNotifications(except RedirectToIdentityProvider) are not fired.
I've downloaded the git source code of Katana project and referenced this project to my instead of the official nuget packages to debug its and as I think currently, I've found the reason why it happens. The AuthorizationCodeReceived "event" method is called from OpenIdConnectAuthenticationHandler class in AuthenticateCoreAsync method. But also, the calling of this method is required that the below checking must give the true result:
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
{
//some necessary preparation to call `AuthorizationCodeReceived` event method
}
As we can see, this checking allows only POST requests and I see these POST requests when I run app in local IIS, but I cannot see these POST requests when I deploy my application in azure portal (I've debugged both of options : on local IIS and in azure portal).
As summary from the above, this is the only one difference between these runnings. (Azure IIS doesn't send POST request at all by some reason).Any other methods in Katana project (which I checked) are called in the same way.
Could anybody help with it?
PS Note, I check any changes only after clearing of browser data (cache/history and so on).
The answer is the following:
The authorization in azure portal should be configured as shown above. In case if you chose LogIn with Azure Active Directory, then app services auth takes place outside of your app, and the custom authorization is not triggered.
We have a SharePoint publishing site with anonymous access hosted on the internet. As per out latest requirements, we need to implement user login (AzureAD, Microsoft personal and work accounts, and more).
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
As per the documentation here, we want to implement this using Web API to get the secure information from the database. We are thinking about using MSAL.js file for user login and logout on the SharePoint and after getting a bearer token we can call the Web API for the additional data from our database.
Standalone Web APIs restriction: “You can use the v2.0 endpoint to build a Web API that is secured with OAuth 2.0. However, that Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID. The client won't be able to request or obtain permissions to your Web API.”
How can we create two applications with same application ID at App Registration Portal? Or should we use the same application ID at SharePoint and Web API’s end?
There is no need to register two application, you only need to one register application. After you register the application, you can using the MSAL library below to get the token to call the web API:
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("e5e5f2d3-4f6a-461d-b515-efd11d50c338", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser();
console.log(token);
// signin successful
}, function (error) {
// handle error
});
</script>
And to protect the web API, you can use the same app and refer the code below:
public void ConfigureAuth(IAppBuilder app)
{
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = "e5e5f2d3-4f6a-461d-b515-efd11d50c338",
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")),
});
}
Is there any (or going to be any) built in support for declaratively securing (i.e. using attributes) REST services for oAuth2?
I would like to specify that the REST services of my SS web service can only be accessed by a client if they specify the oAuth2 'Authorization' header in their request to the service.
I don't want my service to provide authentication to my clients (i.e. no AuthFeature). Clients need to have already done authentication with a oAuth service (i.e. facebook etc.).
Using the [Authenticate] attribute on your Service ensures that only authenticated clients have access.
The Authentication wiki explains how to initialize ServiceStack's built-in AuthFeature to specify only the providers you want to allow clients to authenticate with, e.g. You can ensure clients can only Authenticate with either LinkedIn or Google OAuth2 providers with:
var appSettings = new AppSettings(); //Access Web.Config AppSettings
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new GoogleOAuth2Provider(appSettings), //Sign-in with Goolge OAuth2
new LinkedInOAuth2Provider(appSettings), //Sign-in with LinkedIn OAuth2
}));
Note: OAuth2 requires the additional ServiceStack.Authentication.OAuth2 NuGet package and Web.Config settings, see Auth docs for more info.
Using Request Filters
You can also enforce specific requirements for client requests by a Global Request Filter or opt-in Request Filter Attributes, e.g:
this.RequestFilters.Add((httpReq, httpRes, requestDto) => {
var authHeader = httpReq.Headers[HttpHeaders.Authorization];
if (!IsValidAuthHeader(authHeader)) {
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
httpRes.StatusDescription = "Authentication is required";
httpRes.EndRequest();
}
});
More Service Restrictions
Also related are the Security docs describes how you can declaratively restrict services using the [Restrict] attribute.