Is there a package for authenticating with Azure AD, for Asp.net Core?
For example, the following Authentication packages exist, when querying Nuget:
Microsoft.AspNetCore.Authentication.Facebook
Microsoft.AspNetCore.Authentication.Twitter
Microsoft.AspNetCore.Authentication.Google
Microsoft.AspNetCore.Authentication.MicrosoftAccount
I tried to use the MicrosoftAccount package, but after wiring it up successfully, I got the following error from the Microsoft page:
Application '{my-app-id}' {my-app-name} is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint.
There's examples that don't use a middleware package, but the middleware package offers ease of use, and more directly integrates with the Identity framework.
Are there any direct packages that use Azure AD, or anyway to specify that the MicrosoftAccount package should point to the organizations/azure/tenant url?
This is what worked for me:
authBuilder
.AddMicrosoftAccount(Auth.Constants.AuthenticationScheme, options =>
{
options.ClientId = clientId;
options.ClientId = clientSecret;
if (tenantId != null)
{
var resource = "https://graph.microsoft.com";
options.AuthorizationEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/authorize?resource={resource}";
options.TokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token?resource={resource}";
}
});
tenantId - look at the "Endpoints" section from the "App registrations" blade. You can extract it from the URLs, or use those URLs directly instead of what I've done above.
clientId - this is your "Application ID" in your registered app.
clientSecret - this is a password you create and register under the "Keys" section of your registered app.
You can then get back more information by using the access token with https://graph.microsoft.com, such as adding options like the following:
options.ClaimActions.DeleteClaim(ClaimTypes.Name);
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userPrincipalName");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get the GitHub user
var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JObject.Parse(contents);
context.RunClaimActions(user);
}
};
Related
I want to implement OAuth2.0 token in ASP.NET Core Web API. I have seen many tutorials and videos but all are doing the traditional way or in ASP.NET only not in Core. So I want the way to implement in visual studio 2022 with latest version of ASP.NET Core. Please help
I have seen many tutorials and videos but all are doing the traditional way or in ASP.NET only not in Core. So I want the way to implement in visual studio 2022 with latest version of ASP.NET Core. Please help
You can use Jwt authentication to protect your web api and this is one of the method based on OAuth2.0. Here's a blog and the following codes are based on it.
OAuth2.0 is a protocol but not the implement. So you can't find samples for it. But when you searched Jwt auth, Azure AD into .net 6 or some other products, you will find many doucuments.
Let's see some additional information which may also help you:
Let's go back to the sample, in this scenario, you have to integrate the authentication first. In .net 6, going to program.cs and adding these code:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
//adding jwt auth
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//define which claim requires to check
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
//store the value in appsettings.json
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
...
app.UseRouting();
//adding UseAuthentication
app.UseAuthentication();
app.UseAuthorization();
In appsettings.json:
"Jwt": {
"Key": "ThisismySecretKey",
"Issuer": "Test.com"
}
Then, pls add [Authorize] before the api controller, then you've established the authentication and when accessing the api without the correct jwt token, you will get 401 error:
Let's generate an access token then test calling the api with the token. In another Controller without [Authorize], adding code like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
private IConfiguration _config;
public HomeController(IConfiguration config)
{
_config = config;
}
public IActionResult Index()
{
ViewBag.accessToken = generateJwt();
return View();
}
private string generateJwt() {
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
//If you've had the login module, you can also use the real user information here
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, "user_name"),
new Claim(JwtRegisteredClaimNames.Email, "user_email"),
new Claim("DateOfJoing", "2022-09-12"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Then calling the api with the token, you can decode the token first:
Im having some problems retriving data from sharepoint (Disks) for a dotnet core app.
At the moment my app tries to use the app itself, and not the logged in user to retrive disks, but the prefered way would be to use the accesstoken for the logged in user instead.
Maybe authenticating as the app with clientId and secret wont work with drives at all?
The login works fine.
I've set up a dotnet core app with the following startup:
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(30);
})
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
I also have the following services registered:
services.AddTransient<IAuthenticationProvider, GraphAuthenticationProvider>();
services.AddTransient<IGraphServiceClient, GraphServiceClient>();
services.AddTransient<IGraphProvider, MicrosoftGraphProvider>();
where i use the this to authenticate:
public class GraphAuthenticationProvider : IAuthenticationProvider
{
public const string GRAPH_URI = "https://graph.microsoft.com/";
private string _tenantId { get; set; }
private string _clientId { get; set; }
private string _clientSecret { get; set; }
public GraphAuthenticationProvider(IConfiguration configuration)
{
_tenantId = configuration.GetValue<string>("AzureAd:TenantId");
_clientId = configuration.GetValue<string>("AzureAd:ClientId");
_clientSecret = configuration.GetValue<string>("AzureAd:ClientSecret");
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{_tenantId}");
ClientCredential creds = new ClientCredential(_clientId, _clientSecret);
//I have tried using acquireTokensAsync with scopes, but there is no such method.
AuthenticationResult authResult = await authContext.AcquireTokenAsync(GRAPH_URI, creds);
request.Headers.Add("Authorization", "Bearer " + authResult.AccessToken);
}
}
I have given the app plenty of permissions in the API settings in portal, mostly because im unsure what i need, and at the moment im just eager to make it work first, then refactor some.
The app is able to log in, and retrive the following data with the SDK:
var groups = await _graphServiceClient.Groups[appSettings.AzureAd.GroupId].Request().GetAsync();
however: the following does not work:
var groupDrives = await _graphServiceClient.Groups[appSettings.AzureAd.GroupId].Drives
.Request()
.GetAsync();
and i get the following error:
Code: AccessDenied
Message: Either scp or roles claim need to be present in the token.
I also have user login in startup, and the app wont be used without logging in towards azure AD:
Could i use the accessToken for the user instead?
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
options.TokenValidationParameters.ValidateIssuer = false;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
var roleGroups = new Dictionary<string, string>();
Configuration.Bind("AuthorizationGroups", roleGroups);
var clientApp = ConfidentialClientApplicationBuilder
.Create(Configuration["AzureAD:ClientId"])
.WithTenantId(Configuration["AzureAD:TenantId"])
.WithClientSecret(Configuration["AzureAD:ClientSecret"])
.Build();
var authResult = await clientApp
.AcquireTokenOnBehalfOf(new[] { "User.Read", "Group.Read.All" }, new UserAssertion(ctx.SecurityToken.RawData))
.ExecuteAsync();
var graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
}));
//Could i register the graphservice as a singelton with the users accesstoken?
//Fetching drives here with the accessToken from user works.
var graphService = new GraphService(graphClient, Configuration);
var memberGroups = await graphService.CheckMemberGroupsAsync(roleGroups.Keys);
var claims = memberGroups.Select(groupGuid => new Claim(ClaimTypes.Role, roleGroups[groupGuid]));
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
};
});
I would actually like to use the users accesstoken to retrive the drives etc, but im not sure on how to store\reuse the accesstoken. I should probably register the service as a singelton with the users accesstoken as mentioned in the comment?
I followed this guide, and it has the same classes\services i have used:
http://www.keithmsmith.com/get-started-microsoft-graph-api-calls-net-core-3/
I actually thought the option on top here was just a header. It might be easier now.. https://i.imgur.com/yfZWaoe.png
it feels like you are mixing up a whole bunch of concepts here. that example you are using is based on the client credentials flow. you should probably start by reading up on the different types of authentication flows available. https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows
In general when you use the client credential flow, the permissions you need to set are application permissions in the api permissions blade. Delegated permissions are for user login flows.
when you are using delegated permissions like you are above. and you use a flow that gets user tokens, then the access that the application has is based on the access the user has. for example, if you delegate groups.read.all with delegated permissions, then that gives the application access to read all the groups that That specific user has access to. it doesn't give the application access to all groups. if this is what you want, then by all means use the user flow.
You didn't mention if you were writing a web app, or what, but if you are you may want to look carefully at the on-behalf-of flow. here is an example of it. https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/2-WebApp-graph-user/2-1-Call-MSGraph
but again above applies for the permissions, when you get a user token your app will only have access to the items that user has access to. no more. eg user A has access to sharepoint site A, user B has no access to site A, when you use a user token for user B to call graph it will not return results for site A since user B does not have access to it.
You've defined Delegated scopes but are attempting to authenticate using Client Credentials. Delegated scopes are named such because the User is delegating their access to your application.
You need to request Application scopes when authenticating without a User.
I'm having problems in retrieving access token of an authenticated user. below is my configuration
ASP.NET MVC 5 Client:
OpenIdConnect
IdentityServer3 libraries
ResponseType = "code id_token"
ASP.NET Core Identity Server:
IdentityServer4 libraries
Client Config: AllowedGrantTypes =
GrantTypes.HybridAndClientCredentials,
I'm trying to get the access token in my client using this:
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(TokenEndpoint, "clientid", "secret");
var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
},
I used this reference for above implementation - https://github.com/IdentityServer/IdentityServer3/issues/2457
but the properties in the response has null values. I need the access token so that the user logged in the client can access the api. Below is another way that i'm trying to retrieve the access token:
public async Task<ActionResult> CallApiUsingUserAccessToken()
{
var user = User as ClaimsPrincipal;
var accessToken = user.FindFirst("access_token").Value;
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await client.GetStringAsync("http://localhost:6001/api/values");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
however, user.FindFirst("access_token").Value; is null. I'm thinking of migrating my MVC client to Core because I've tried the IdentityServer4 version in an asp.net core but that seems to be a big migration to my part. Thank you.
[updated]
It never occured to me that the endpoints in the IdentityServer3 differs from IDS4. I did have to change var tokenClient = new TokenClient(TokenEndpoint, "client", "secret"); to var tokenClient = new TokenClient("http://localhost:9000/connect/token", "client", "secret") since TokenEndpoint in IDS3 is http://localhost:9000/core/connect/token which the endpoint "core" does not exist in IDS4. I'm able to get the access token in this line var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri); but after authorization, i'm still getting nullreference exception to this var accessToken = user.FindFirst("access_token").Value; line of code.
Given the IdentityServer 4 documentation on
Switching to Hybrid Flow and adding API Access back
and an example client from IdentityServer3.Samples
MVC OWIN Client (Hybrid)
you should be able to setup a working environment.
To support debugging you should always do proper response handling as shown in example below and copied from example client. Add any response errors to your question.
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
Constants.TokenEndpoint,
"mvc.owin.hybrid",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
Finally I recommend to add code for all important parts of an IdentityServer3/4 based setup - because the truth is usually burried in the details.
According to these posts, https://github.com/IdentityServer/IdentityServer3/issues/2457 & https://github.com/IdentityServer/IdentityServer3/issues/2015#issuecomment-172623173, it is a good practice to not include the access token in the claims. Hence, I followed his example, https://github.com/Mich-b/IdentityServerTMLClient/blob/master/IdentityServerTMLClient/Startup.cs, in which the access token is added in the Http Session storage.
I'm working on an ASP.Net project, which needs to be deployed after completion on PaaS, which needs to be BlueMix (It wasn't my choice, It was an order).
In addition I need to use an:
Active Directory or LDAP to the User Authentication and Authorization, integrated with the ASP.Net Project.
The Issues Here Are :
1. I have found an integration to the Active Directory or SSO Services using only Java or Node.js, but in my case I am using ASP.Net
2. I want a solution for how the integration can be done on top of the PaaS between the Active Directory and ASP.Net application.
Depending on which version of ADFS you're using, you should be able to use either OAuth or OIDC middleware to connect from an ASP.NET Core application (assuming you're using ASP.NET Core because you're using Bluemix). If you're using at least ADFS 3.0 (Windows Server 2012+), you can use ASP.NET Core's generic OAuth middleware to connect.
First, create a configuration file to store your ADFS server settings, or modify an existing configuration file (such as appsettings.json).
Sample "adfs-settings.json" file:
{
"ADFS": {
"ClientId": "Your ClientId as set on ADFS server",
"ResourceUrl": "url of this application (ex: http://mywebapp.mybluemix.net)",
"ServerUrl": "url of ADFS (ex: https://dc.your.domain)"
}
}
If you created a new file, such as "adfs-settings.json", for your ADFS configuration, add it to your Configuration object in the constructor of your Startup.cs file.
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("adfs-settings.json");
Configuration = builder.Build();
}
In your Configure method of Startup.cs create an OAuthOptions object:
var options = new OAuthOptions();
options.AutomaticChallenge = true;
options.AuthenticationScheme = "ADFS";
Specify the ClientId that you created when configuring this application on your ADFS server by reading it from your Configuration object. The generic OAuth middleware also requires that you provide a ClientSecret here even though that value is not actually used by ADFS 3.0.
options.ClientId = Configuration["ADFS:ClientId"];
options.ClientSecret = "ADFS 3.0 does not support confidential client, but OAuth middleware requires it";
Set the callback url which the ADFS server will redirect to in your application.
options.CallbackPath = new PathString("/signin-adfs");
Now configure the OAuthEvents. OnRedirectToAuthorizationEndpoint defines parameters which are passed to the ADFS authorization endpoint when the application determines that a user needs to be authorized. This will require at least a resource parameter which points to the url of your application. OnCreatingTicket is triggered when the ADFS server has finished authorizing a client and returns a JWT token containing claims data to your application. In this method you'll need to process adding roles and claims to the HttpContext object.
options.Events = new OAuthEvents {
OnRedirectToAuthorizationEndpoint = context =>
{
var parameter = new Dictionary<string, string>
{
["resource"] = Configuration["ADFS:ResourceUrl"]
};
var query = QueryHelpers.AddQueryString(context.RedirectUri, parameter);
context.Response.Redirect(query);
return Task.CompletedTask;
},
OnCreatingTicket = context => {
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken validatedToken = tokenHandler.ReadJwtToken(context.AccessToken);
IEnumerable<Claim> a = validatedToken.Claims;
foreach (var claim in a)
{
// role claim needs to be mapped to http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// for IsInRole() to work properly
if (claim.Type == "role")
{
context.Identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
}
else if (claim.Type == "unique_name")
{
// map name to Identity.Name
context.Identity.AddClaim(new Claim(context.Identity.NameClaimType, claim.Value));
}
else
{
// this is optional, if you want any other specific claims from Active Directory
// this will also include some information about the jwt token such as the issue
// and expire times
context.Identity.AddClaim(new Claim(claim.Type, claim.Value));
}
}
return Task.CompletedTask;
}
};
Next, set the ClaimsIssuer to the ADFS url and set the SignInScheme to CookieAuthenticationDefaults.AuthenticationScheme and configure the AuthorizationEndpoint and TokenEndpoint to the proper endpoints on your ADFS server.
options.ClaimsIssuer = Configuration["ADFS:ServerUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.AuthorizationEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/authorize/";
options.TokenEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/token/";
Finally, add the OAuth middleware using the options you've just created:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOAuthAuthentication(options);
Now you should be able to apply the [Authorize] attribute to any controller or action which requires authorization with ADFS. For a complete sample application see this GitHub repo.
I'm having troubles with OAuth .NET backend authentication for Azure mobile-services in ASP.NET 5.0. I'm trying to implement external login with Facebook,Twitter,Google and Microsoft.
I'm successfully getting access_token from all external sources and then trying to log in into MobileServiceClient.
here is my code
var app = System.Web.HttpContext.Current.Items["AzureClient"] as MobileServiceClient;
app.Logout();
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
var accesToken = loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == "access_token");
MobileServiceUser user = null;
if (providerName == "Microsoft")
{
user = await app.LoginWithMicrosoftAccountAsync(accessToken);
}
else
{
var token = new JObject();
token.Add("access_token", accessToken);
user = await app.LoginAsync(loginInfo.Login.LoginProvider, token);
}
And I'm getting authenticated but only with facebook token. Microsoft and Google throw 401 unauthorized exception. Twitter throws "Method not allowed". What am I dowing wrong?
I've double-checked that app secret and app keys are populated for all providers in azure management portal.
Please, help
I'm not sure if tokens from social network can be forwarded to MobileServiceClient or not but it works with facebook and doesn't work with all the others. I'm really puzzled about this behaviour;
I finally ended up with creating an ActiveDirectory application and using ADAL AcquireToken method to obtain AD token for my MobileServicesClient. As it is described here
Azure Website Single Sign On accessing Azure Mobile Service from Azure Active Directory as User
here is my Method obtaining token from AD
private string GetAdToken()
{
string clientID = "<clientId>";
string authority = "<AuthorityUrl>";
string resourceURI = "<WebApiUrl>";
var appKey = "<applicationKey>";
var ac = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
var clientCredential = new ClientCredential(clientID, appKey);
var ar = ac.AcquireToken(resourceURI, clientCredential);
Session["token"] = ar.AccessToken;
return ar.AccessToken;
}
and here is my method which is run before quering Azure datatables through MobileServiceClient.
private async Task<MobileServiceUser> EnsureLogin()
{
var app = System.Web.HttpContext.Current.Items["AzureClient"] as MobileServiceClient;
app.Logout();
JObject token = new JObject();
token["access_token"] = Session["token"].ToString();
return await app.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, token);
}
So now it doesn't metter what provider I use to log in to my web application. MobileServiceClient always works with ad token.
I'm not sure if it is an acceptable practice but it works and maybe this will help somebody like me struggling against azure authentication