Azure Ad integration in Azure functions with swagger - azure

I am using Azure functions V3 with swagger UI. I am using the below code in my start up
builder.AddSwashBuckle(Assembly.GetExecutingAssembly(),opts => {
opts.SpecVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;
opts.Title = "Platform Data Operations";
});
I have configured the Azure Ad authentication for my app in the azure portal.
Set up the permissions and redirect Uri.I am able to login successfully from the browser after calling the redirect URI.
Can some one help me with how to integrate in the code.

You can follow the sample code below,
builder.AddSwashBuckle(Assembly.GetExecutingAssembly(), opts =>
{
opts.SpecVersion = OpenApiSpecVersion.OpenApi3_0;
opts.ConfigureSwaggerGen = (x =>
{
x.DocumentFilter<CustomSwaggerDocumentAttribute>();
x.EnableAnnotations();
x.CustomOperationIds(apiDesc =>
{
return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)
? methodInfo.Name
: new Guid().ToString();
});
});
// remove code as query parameter
opts.AddCodeParameter = false;
opts.PrependOperationWithRoutePrefix = true;
opts.Title = "Sample Function API Swagger";
});

Related

Call ASP .NET Core API in Azure from daemon application

I have limited experience on Azure AD and authentication mechanism. So far I cannot figure out why is not working. Here is the scenario:
I have an ASP net core 2.1 application deployed in azure web app service.
For authentication I’m using Open ID connect with .AddOpenIdConnect and provide client_id, secret_id, etc. When users are accessing my web API they are redirected to Microsoft login.
Now I need to expose an API to a third party application (scheduled web job) which is not in Azure.
I tried to use this sample from Microsoft, only the console app, as I already have the WebApp in Azure.
Running the sample I’m able to get the token, but when I call my API the response is the HTML to Microsoft login page.
On Azure portal on
Enterprise Application -> daemon-console -> Activity -> Service Principal sign-ins
I can see the success sign in.
Note: for testing I run the web app on my local machine and from the console application I’m calling API https://localhost:44306/api/test.
Asp .net core app:
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(option =>
{
option.Cookie.Name = "myWebApp";
option.Cookie.SecurePolicy = CookieSecurePolicy.Always;
option.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect(option =>
{
option.ClientId = client_id;
option.ClientSecret = client_secret;
option.Authority = authority;
option.SignedOutRedirectUri = "http://localhost:44306/";
option.CorrelationCookie.Name = "myWebAppCorrelation";
option.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
option.NonceCookie.Name = "WebAppNonce";
option.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
option.Resource = "https://graph.windows.net";
option.ResponseType = "id_token code";
})
Console app trying to access the API ( code extracted from Microsoft sample )
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); // ok
var httpClient = new HttpClient();
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(webApiUrl);
if (response.IsSuccessStatusCode) // ok
{
string json = await response.Content.ReadAsStringAsync(); // here I'm getting the HTML to login page
var result = JsonConvert.DeserializeObject<List<JObject>>(json);
Console.ForegroundColor = ConsoleColor.Gray;
processResult(result);
}
The only difference between the sample code and my scenario is that the web app from sample is using services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration)
but I cannot use .AddMicrosoftIdentityWebApi in Asp .Net core 2.1
Does anyone has an idea where the issue might be ? Do I need to add another authentication scheme ?
You need to support JWT authentication in addition to cookie authentication. So you need to add AddJwtBearer. You also need to extend the authentication check because you are now supporting multiple schemes.
This is how I would do it:
// public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, cfg => {
cfg.Authority = authority;
cfg.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = /*see scopes of your deamon app*/
};
})
.AddCookie(option =>
{
// ...
})
.AddOpenIdConnect(option =>
{
// ...
})
// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthentication();
// https://github.com/aspnet/Security/issues/1847
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (result.Succeeded)
{
context.User = result.Principal;
}
}
await next();
});
app.UseAuthorization();

Microsoft.Identity.Web and ASP.NET Core SignalR JWT authentication

I am using ASP.NET Core to make a web application that also uses SignalR Core to provide real time functionality. I use Azure AD B2C for user management. I have successfully used Microsoft.Identity.Web (https://github.com/AzureAD/microsoft-identity-web) to secure my API endpoints using tokens generated by Azure AD B2C.
I would like to do the same for my SignalR Core hubs. The documentation reads to add the appropriate annotation to your hubs/methods, which I have done. SignalR's client side library adds the access token as a query parameter which must be extracted and added to the context manually in the configuration of your ASP.NET core application, like so.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
However, this seems to be incompatible with the configuration supplied by Microsoft.Identity.Web, here:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));
How can I make SignalR work with Microsoft.Identity.Web?
That should do it:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
Func<MessageReceivedContext, Task> existingOnMessageReceivedHandler = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await existingOnMessageReceivedHandler(context);
StringValues accessToken = context.Request.Query["access_token"];
PathString path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = accessToken;
}
};
});
Instead of adding a JwtBearer, you can configure the JwtBearerOptions object this way.
Adapted from this documentation: https://github.com/AzureAD/microsoft-identity-web/wiki/customization
You can use Visual Studio to set up the SignalR connection, and then just add this line in startup.cs (VS might add it automatically)
services.AddSignalR().AddAzureSignalR();
This dev sample has SignalR set up, just the connection string is missing, but might give you an idea of what to do. Most of this was done automatically with VS. If you have issues setting it up, please open an issue in the repo. thanks.

Azure B2C with Web API

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

How to enable App Service Mobile App SSO for UWP

I am building a Universal Windows Platform (UWP) app that uses the Azure App Service Mobile App backend as well as the user's OneDrive account. I have 2 requirements for authentication:
If the user is logged in to their UWP device with a Microsoft account (e.g. Windows 10) then I don't want them to be presented with a login prompt (i.e. Single Sign On, re-using their Microsoft account credentials).
I want to have a single authentication event across Azure & OneDrive, i.e. the user authorises once and I re-use that token for both services.
I did this in Windows Phone 8 with an Azure Mobile Service by logging in with the Live SDK and then passing the returned token to the MobileServiceClient.LoginAsync() method, however I can't get this to work in UWP with an Azure Mobile App. When I call that same method I receive a 401 Unauthorised response.
I have associated my UWP app with the store and set up the
application at the Microsoft Account Developer Centre, including
adding the redirect URI from the Azure Mobile App.
I have set up the Azure App Service Mobile App, including adding the
Client ID & Secret from the Microsoft Account Developer Centre.
I have tried numerous ways to retrieve the token, including the
OnlineIdAuthenticator, WebAuthenticationCoreManager and
WebAuthenticationBroker. None has worked so far.
I currently use the following code in a class LiveAuthenticationService to retrieve an access token:
public async Task<bool> LoginAsync()
{
AccessToken = null;
bool success = false;
OnlineIdAuthenticator onlineIdAuthenticator = new OnlineIdAuthenticator();
EventWaitHandle waithandle = new ManualResetEvent(false);
OnlineIdServiceTicketRequest serviceTicketRequest = new OnlineIdServiceTicketRequest(scopes, "DELEGATION");
UserIdentity result = await onlineIdAuthenticator.AuthenticateUserAsync(serviceTicketRequest);
if (!string.IsNullOrWhiteSpace(result?.Tickets[0]?.Value))
{
currentUserId = result.SafeCustomerId;
AccessToken = result.Tickets[0].Value;
success = true;
waithandle.Set();
}
else
{
await logger.LogErrorAsync("Error signing in to Microsoft Live",
new Dictionary<string, string> { { "errorCode", result?.Tickets[0]?.ErrorCode.ToString() } });
}
waithandle.WaitOne(10000); //10 second timeout
return success;
}
And then this to attempt to login to my Azure Mobile App with that token, which uses LiveAuthenticationService from above:
private async Task RefreshUserIdAndAccessToken()
{
try
{
var tcs = new TaskCompletionSource<MobileServiceUser>();
var authService = new LiveAuthenticationService();
await UiDispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
try
{
await authService.LoginAsync();
var jsonAuthenticationToken = JObject.Parse(#"{""authenticationToken"": """ + authService.AccessToken + #"""}");
tcs.SetResult(await mobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jsonAuthenticationToken));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
var user = await tcs.Task;
currentUserId = user.UserId;
AccessToken = user.MobileServiceAuthenticationToken;
}
catch (Exception ex)
{
await logger.LogExceptionAsync(ex,
Constants.LOGGING_DATAKEY_REFRESHACCESSTOKENFAILURE,
currentUserId);
currentUserId = null;
AccessToken = null;
}
}
As stated this results in a 401 Unauthorised response from Azure. I have run Fiddler and the request seems to be correct, the expected authentication token is included in a JSON payload with the request.
UPDATE
One thing I can see is that the token issued by the code above is almost 900 characters long, all in the form YnElFkAAcK8bRSQab/FK+PT5n/wA4CPU..., while the token issued if I let Azure Mobile App handle the authentication, i.e. call MobileServiceClient.LoginAsync() without passing a token, is only about 350 characters long and in the form hbGciOi.eyJmdWWxsIiwiRGJn... (notice the period towards the beginning).
This issue is really causing me problems now. I can't release the app without the authentication working and I can't figure out how to fix it. Any help will be appreciated.
This was a tough one for me to solve as I was facing this problem too.
The most important part is the OnlineIdServiceTicketRequest the request should look like this:
var mobileServicesTicket = new OnlineIdServiceTicketRequest("https://yourmobileservice.azure-mobile.net/", "JWT");
Note that we are specifying your endpoint and also requesting a JWT token instead of delegation. This will get the 350ish character token you were looking for.
Here is a full code sample of what I'm doing:
public async Task<bool> LoginAsync()
{
var authenticator = new Windows.Security.Authentication.OnlineId.OnlineIdAuthenticator();
var mobileServicesTicket = new Windows.Security.Authentication.OnlineId.OnlineIdServiceTicketRequest("https://yourendpoint.azure-mobile.net/", "JWT");
var ticketRequests = new List<OnlineIdServiceTicketRequest>() { mobileServicesTicket };
var authResult = await authenticator.AuthenticateUserAsync(ticketRequests, CredentialPromptType.PromptIfNeeded);
if ((authResult.Tickets.Count == 1) && (authResult.Tickets[0].ErrorCode == 0))
{
var accessToken = authResult.Tickets[0];
var res = await _mobileServiceClient.LoginWithMicrosoftAccountAsync(accessToken.Value);
return true;
}
else
{
return false;
}
}
_mobileServiceClient is injected into the class and is a reference to Microsoft.WindowsAzure.MobileServices.MobileServiceClient object within the WindowsAzure.MobileServices library.
I actually ended up writing a blog article about this problem here http://jshapland.com/single-sign-on-with-azure-mobile-services-in-a-uwp-app/

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