Azure B2C with Web API - azure

The examples I've seen for using Azure B2C with Web API show app.UseOAuthBearerAuthentication (as shown below), however my ASP .NET 5 Web API project uses IApplicationBuilder (not IAppBuilder) and UseOAuthBearerAuthentication does not exist. I've tried app.UseOpenIdConnectAuthentication, however I believe this uses cookies and I couldn't get it working using a Xamarin app as a client. I've tried app.UseWindowsAzureActiveDirectoryBearerAuthentication but I believe this is for standard Azure AD (not B2C) is that true? Any ideas how to get Azure B2C working with the very latest ASP .NET Web API?
Thanks!!!
public void ConfigureAuth(IAppBuilder app)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = clientId,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, "v2.0", discoverySuffix, commonPolicy)))
});
}

This works for me. I hope it helps someone else who is looking to use Azure B2C with the latest .NET Web API framework:
public void ConfigureAuth(IApplicationBuilder app, IOptions<PolicySettings> policySettings)
{
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
MetadataAddress = "https://login.microsoftonline.com/[my-tenant].onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_my-signup-signin-policy",
Audience = "[My-Azure-App-Guid]",
Events = new JwtBearerEvents
{
OnTokenValidated= ctx =>
{
var nameClaim = ctx.AuthenticationTicket.Principal.FindFirst("name");
if (nameClaim != null)
{
var claimsIdentity = (System.Security.Claims.ClaimsIdentity)ctx.AuthenticationTicket.Principal.Identity;
claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, nameClaim.Value));
}
return Task.FromResult(0);
},
OnAuthenticationFailed = ctx =>
{
ctx.SkipToNextMiddleware();
return Task.FromResult(0);
}
}
});
}

If the JWT token is available in your client, then you can use JwtBearerAuthentication in the Web API. The JWT issued by Azure B2C ( social logins) can be used as a token to get authenticated in the Web API.
Refer to https://github.com/sendhilkumarg/AzureB2CWebAppAndAPIAuthentication
In this sample the client is a web app

Related

How to refresh access token in web application using Azure Active Directory

I'm currently struggling with access token lifetime. I have dotnet core Web Application and dotnet core Web API.
The web application is protected with OpenIDConnect authorization. Once you try to connect into web app, you are redirected to Microsoft login form and after successful login, the Access Token is provided and stored into cookie together with Refresh Token.
Therefore, the Access Token is passed in Authorization Header for my WebAPI request.
When the access_token lifetime expires, then my WebAPI starts to return 401 Unauthorized.
I read a lot articles about revoking access token by using refresh token, but I didn't find any implementation example, so I turn to you guys.
This is how I am setting up the OpenId in Web Client.
services.AddDataProtection();
services.AddAuthorization();
services.AddWebEncoders();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ClientId = Configuration["AzureAd:ClientId"];
options.Authority = $"{Configuration["AzureAd:AadInstance"]}{Configuration["AzureAd:Tenant"]}/v2.0";
options.ClientSecret = Configuration["AzureAd:ClientSecret"];
options.ResponseType = "code";
options.SaveTokens = true;
options.UseTokenLifetime = true;
options.Scope.Add(Configuration["AzureAd:Scope"]);
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = Configuration["AzureAd:Tenant"] != "common",
RoleClaimType = JwtClaimTypes.Role
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
context.Response.Redirect("/error");
return Task.CompletedTask;
}
};
});
services.AddHttpContextAccessor();
This is how I am setting up authentication in Web API Startup.cs.
services.AddAuthentication("Bearer")
.AddJwtBearer(
"Bearer",
options =>
{
options.Authority = $"{Configuration["AzureAd:AadInstance"]}{Configuration["AzureAd:Tenant"]}/v2.0";
options.Audience = Configuration["AzureAd:Audience"];
options.TokenValidationParameters.ValidateIssuer = false;
});
And lastly, this is constructor of my ApiService, where I am adding access token to headers.
protected ApiService(HttpClient httpClient, string apiUri, IHttpContextAccessor httpContextAccessor, ILogger<ApiService> logger)
{
this.httpClient = httpClient;
this.apiUri = apiUri;
this.logger = logger;
context = httpContextAccessor.HttpContext;
this.httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", context.GetTokenAsync("access_token").Result);
}
If you need guys any more information, tell me and I will provided it. Thank you!
As I undarstand now - you have basic ASP .NET Core Web application (MVC, or Razor) and you want to secure it with Azure AD.
If my understanding is correct, you should leverage Microsoft.Identity.Web library:
https://github.com/AzureAD/microsoft-identity-web
It is currently still in preview but I can confirm that it works stable.
Here is detailed instruction how to integrate it with ASP .NET Core web app:
https://github.com/AzureAD/microsoft-identity-web/wiki/web-apps
Here are the samples:
https://github.com/AzureAD/microsoft-identity-web/wiki/web-app-samples
This library also manages refreshing token and provides token cache implementation so you do not have to implement it on your own:
https://github.com/AzureAD/microsoft-identity-web/issues/221
Reprogramming the authentication in Startups and take advantage of ITokenAcquirer service solve my problem: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/4-WebApp-your-API/4-1-MyOrg

OWIN Authentication with Azure AD B2C OP: SIgnOut Doesn't Remove Cookie when Called from External iFrame

I have an authentication module in Umbraco using OWIN/OIDC, authenticating against our Azure AD B2C resource. As part of this module, there is a LogOut controller method, which is working correctly.
We are trying to develop single sign out for applications within our Azure tenant. We are still working on having Azure AD B2C call the logout method for each application. In order to test initiation of sign out from other applications, I have set up an iframe in one of our custom applications (also authenticating via Azure AD B2C) that calls the LogOut method in our Umbraco implementation when users sign out from that application. I can see that the LogOut method is being called when the external method opens the iframe, and all of the objects look the same as when the method is called from within Umbraco. However, the user is not logged off of the application. The authentication cookie, which is .AspNet.ApplicationCookie, has SameSite as None, Secure as true and HttpOnly as false, but it is not removed as it is when Umbraco calls the method.
Any tips on how to get the LogOut method to work from the external application would be appreciated.
Here is my configuration:
private void ConfigureAzureB2CAuthentication(object sender, OwinMiddlewareConfiguredEventArgs args) {
//get appbuilder
AppBuilder app = (AppBuilder)args.AppBuilder;
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(Current.Factory.GetInstance<FrontEndCookieAuthenticationOptions>(), PipelineStage.Authenticate);
//Set configuration on appbuilder
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
MetadataAddress = string.Format(
ConfigurationManager.AppSettings["ida:AzureInstance"],
ConfigurationManager.AppSettings["ida:Tenant"],
ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"]),
ClientId = ConfigurationManager.AppSettings["ida:ClientId"],
RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"],
Notifications = new OpenIdConnectAuthenticationNotifications {
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
},
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
NameClaimType = ConfigurationManager.AppSettings["ida:ClaimsLabelEmail"],
ValidateIssuer = false
},
Scope = ConfigurationManager.AppSettings["ida:ScopesOpenIDConnect"],
});
//reafirm backoffice and preview authentication
app.UseUmbracoBackOfficeCookieAuthentication(_umbracoContextAccessor, _runtimeState, _userService, _globalSettings, _securitySection, PipelineStage.Authenticate)
.UseUmbracoBackOfficeExternalCookieAuthentication(_umbracoContextAccessor, _runtimeState, _globalSettings, PipelineStage.Authenticate)
.UseUmbracoPreviewAuthentication(_umbracoContextAccessor, _runtimeState, _globalSettings, _securitySection, PipelineStage.PostAuthenticate);
}
and this is the LogOut method:
public void LogOut(string redirectUrl = "/") {
if (Request.IsAuthenticated) {
RemoveLoggedInMemberAccessToken();
IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
AuthenticationProperties authenticationProperties = new AuthenticationProperties { RedirectUri = redirectUrl };
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties, authTypes.Select(t => t.AuthenticationType).ToArray());
}
}

How to obtain the token returned from Azure AD B2C in ASP Core 2.0?

I have used Visual Studio's latest New Project wizard to create a ASP Core 2.0 Web page (Razor Pages) that uses Individual Accounts as my authentication option. I have created an Azure AD B2C tenant and validated that it works properly.
When I run the web application that was created by the wizard and click Log In in the upper right, it redirects to my Azure AD B2C site, and I can properly login.
After login, the callback url goes to the endpoint configured in my user secrets:
...
"CallbackPath": "/signin-oidc",
...
That all seems to work properly. I understand that the Azure AD B2C portal sends a token back to the above /signin-oidc callback path and stores it.
How can I retrieve the value of that token?
I've been following all of the Azure AD B2C guides, but not all of them have been updated to ASP Core 2.0, and none of them seem to use the code generated from the 15.4 VS wizard such:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options))
.AddCookie();
services.AddMvc();
}
Note: the .AddAzureAdB2C(...)
None of the B2C samples are using this so its difficult for me to follow.
My end goal is to get the token and use that in a strongly-typed set of API classes I generated from Swagger using Autorest which require the token.
The best way to do this is outlined in the Azure AD B2C .Net Core sample, specifically the branch for Core 2.0.
In the normal model/flow, your application will get an id_token and an authorization code but not a token. The authorization code needs to be exchanged for a token by your middle tier. This token is what you'd then be sending over to your web API.
The way to do this involves the following:
Ensure your middle tier is requesting id_token+code for your primary policy (you don't want to do this for your edit profile or password reset policies). From the sample's OpenIdConnectOptionsSetup.cs#L77:
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
!policy.Equals(defaultPolicy))
{
context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
}
else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
{
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
// -----------------------------
// THIS IS THE IMPORTANT PART:
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
// -----------------------------
}
return Task.FromResult(0);
}
Exchange the code for a token. From the sample's OpenIdConnectOptionsSetup.cs#L103-L124:
public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Use MSAL to swap the code for an access token
// Extract the code from the response notification
var code = context.ProtocolMessage.Code;
string signedInUserID = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, AzureAdB2COptions.ApiScopes.Split(' '));
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
You can then use this token elsewhere in your code to call an API. From the sample's HomeController.cs#L45-L57:
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, cca.Users.FirstOrDefault(), AzureAdB2COptions.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdB2COptions.ApiUrl);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
The ASP.NET Core team has created 2 excellent documents on this process.
Cloud authentication with Azure Active Directory B2C in ASP.NET Core
Use Visual Studio to create an ASP.NET Core web app configured to use the Azure AD B2C tenant for authentication
Cloud authentication in web APIs with Azure Active Directory B2C in ASP.NET Core
Use Visual Studio to create a Web API configured to use the Azure AD B2C tenant for authentication

Azure Multi-Tenant Application with Windows Live ID Authentication

We are successfully authenticating the Azure AD users from different subscription using Azure AD Multi-tenant application but unable to authenticate the Windows Live ID accounts.
To authenticate the live ID accounts we use the Windows Live ID identity provider with Azure Access Control Service (ACS), its working fine with Azure AD single tenant application but we are struggling to authenticate Azure AD users across subscriptions which can only be done by using the Azure AD multi-tenant application.
We follow this blog https://msdn.microsoft.com/en-us/library/azure/dn486924.aspx and it works for Single tenant application but when we try to configure the Azure AD app to multi-tenant and configure it with ACS getting the below error.
enter image description here
Is there any approach we authenticate the Windows Live ID and use the Azure Multi-Tenant Application?
You can authenticate Microsoft Account (live id) users in a multi tenant application by skipping ACS altogether and provisioning the Microsoft Account in directory tenants. One gotcha is that authenticating with a Microsoft Account requires you to fully specify the authentication endpoints by instantiating the tenant in the URL. You cannot use the /common endpoint because that relies on the user's home tenant, and an MSA user does not have one.
You add following code in your Account controller
public void SignIn(string directoryName = "common")
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Environment.Add("Authority", string.Format(ConfigurationManager.AppSettings["ida:Authority"] + "OAuth2/Authorize", directoryName));
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
and add this block in your startup.auth.cs
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
// we inject our own multitenant validation logic
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
object obj = null;
if (context.OwinContext.Environment.TryGetValue("Authority", out obj))
{
string authority = obj as string;
if (authority != null)
{
context.ProtocolMessage.IssuerAddress = authority;
}
}
if (context.OwinContext.Environment.TryGetValue("DomainHint", out obj))
{
string domainHint = obj as string;
if (domainHint != null)
{
context.ProtocolMessage.SetParameter("domain_hint", domainHint);
}
}
context.ProtocolMessage.RedirectUri = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path);
context.ProtocolMessage.PostLogoutRedirectUri = new UrlHelper(HttpContext.Current.Request.RequestContext).Action
("Index", "Home", null, HttpContext.Current.Request.Url.Scheme);
//context.ProtocolMessage.Resource = GraphAPIIdentifier;
context.ProtocolMessage.Resource = AzureResourceManagerIdentifier;
return Task.FromResult(0);
},
...
}
When you click on "SignIn" ask for "Azure AD name". Pass that variable to the Account/SignIn action. If the user will be present in the mentioned Azure AD, sign-in will be successful.

MVC 5 OWIN External Login with Mobile Services

I am doing external login (Facebook, Twitter, Microsoft) using MVC 5 OWIN Identity 2, which works great, but I need to access a mobile services with this credential, I have read that to this I need a access token, so I get the access token and try to pass it to the mobile services, but always has this error:
Facebook: Error:
The Facebook Graph API access token authorization request failed with HTTP status code 400
Microsoft: Error:
Invalid token format. Expected Envelope.Claims.Signature.
The method that I am trying to use with mobile services is:
await mobileservi.LoginAsync(MobileServiceAuthenticationProvider.[ProviderName], token);
I read on this link:
http://msdn.microsoft.com/en-us/library/dn296411.aspx
So I am using a JObject() to pass the access token
The format of the token that I most pass:
For Microsoft is:
token.Add("authenticationToken", _accessToken);
{"authenticationToken":"<authentication_token>"}
For Facebook is:
token.Add("access_token", _accessToken);
{"access_token":"<access_token>"}
But I do not have the format for Twitter.
Now according to Azure Mobile Services documentation, I most use the azure mobile services URL on my apps for any of this providers, but if I do this, I receive an error of incorrect URL when redirecting to the provider log in page.
I read this post with OAuth:
http://blogs.msdn.com/b/carlosfigueira/archive/2013/06/25/exposing-authenticated-data-from-azure-mobile-services-via-an-asp-net-mvc-application.aspx
It has to be something like this for MVC 5 OWIN Identity 2.
On the Startuo.Auth.cs file, I have this configure to get the access token for each provider:
Microsoft:
var MicrosoftOption = new MicrosoftAccountAuthenticationOptions()
{
ClientId = "0000000048124A22",
ClientSecret = "c-gTye48WE2ozcfN-bFMVlL3y3bVY8g0",
Provider = new MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
return Task.FromResult(0);
}
}
};
Twitter:
var twitterOption = new TwitterAuthenticationOptions()
{
ConsumerKey = "ConsumerKey",
ConsumerSecret = "ConsumerSecret",
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstoken", context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstokensecret", context.AccessTokenSecret));
return Task.FromResult(0);
}
}
};
Facebook:
var facebookOption = new FacebookAuthenticationOptions()
{
AppId = "AppId",
AppSecret = "AppSecret",
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
return Task.FromResult(0);
}
}
};
On the externalLoginCallback, this is how a retrieve the access token
string email = null;
string accessToken = null;
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
switch (login.LoginProvider)
{
case "Facebook":
accessToken = ext.Claims.First(x => x.Type.Contains("access_token")).Value;
break;
case "Twitter":
accessToken = ext.Claims.First(x => x.Type.Contains("accesstoken")).Value;
break;
case "Microsoft":
accessToken = ext.Claims.First(x => x.Type.Contains("access_token")).Value;
break;
}
Later I store this value on a session variable, this value is the one that I use to pass as the access token.
So I have no idea what to do, can anyone please help me?
OK, I found what I was doing wrong, in order to respect the authorization flow, I must have APP ID and APP Secret that I register on my app (Google, Facebook, Microsoft, Twitter), on my mobile service. This is the important part, the register URL in the app must be the URL of the web site, after doing this, everything work fine

Resources