I have completed the guide here to add Azure AD authentication to my application:
https://azure.microsoft.com/en-gb/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/
and can log in successfully, have a service principal and everything works as expected.
I now want to make web requests as the user, but can't see how to get the authentication details to send in the request, I've tried looking through the ClaimsPrincipal.Current object, but there is nothing i can pass to a HTTP client to make the request.
The sample web app you refered to only signs the user in, but you need to get the access token on behalf of that user to access the api.
You can refer to this sample. This sample calls another webapi, you can ignore that part, just change the resource to https://management.core.windows.net/
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = _azureOptions.Authority;
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
options.ClientSecret = _azureOptions.ClientSecret;
options.Resource = "https://management.core.windows.net/"; // management api
options.ResponseType = "id_token code";
// Subscribing to the OIDC events
options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived;
options.Events.OnAuthenticationFailed = OnAuthenticationFailed;
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Acquire a Token for the management API
string userObjectId = (context.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
var authContext = new AuthenticationContext(context.Options.Authority, new NaiveSessionCache(userObjectId, context.HttpContext.Session));
var credential = new ClientCredential(context.Options.ClientId, context.Options.ClientSecret);
var authResult = await authContext.AcquireTokenAsync(context.Options.Resource,credential);
// Notify the OIDC middleware that we already took care of code redemption.
context.HandleCodeRedemption(authResult.AccessToken, context.ProtocolMessage.IdToken);
}
Related
I'm running .NET Core v3.1 and Blazor and have implemented authorization using Google limited to our domain in Google G Suite as mentioned here: https://www.jerriepelser.com/blog/forcing-users-sign-in-gsuite-domain-account/
Login/logout is working fine, but when the user who logged in is blocked or removed in Google G Suite the user stays logged in into my application until he logs out from the application. When he doesn't logs out he can keep using the application.
I'm looking for a refresh every hour.
This is my login.cshtml.cs:
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
// Get the information about the user from the external login provider
var GoogleUser = User.Identities.FirstOrDefault();
if (GoogleUser.IsAuthenticated)
{
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
RedirectUri = Request.Host.Value,
IssuedUtc = System.DateTime.UtcNow,
ExpiresUtc = System.DateTime.UtcNow.AddHours(1)
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(GoogleUser), authProperties);
}
return LocalRedirect("/");
}
I already added IssuedUtc and ExpiresUtc but that didn't change anything.
You have to enable the ability to call Google APIs (https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group) to get this information, but, before you can do that, the G-Suite Domain Admin has to authorize that access using https://developers.google.com/admin-sdk/directory/v1/guides/authorizing
This explains the process:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
You will want to see this GitHub repo for code samples:
https://github.com/googleapis/google-api-dotnet-client
Here is some psudo code:
string[] Scopes = {
DirectoryService.Scope.AdminDirectoryGroup,
DirectoryService.Scope.AdminDirectoryUser
};
GoogleCredential credential;
//redirectUrl = this.Request.Host.Value;
string keyfilepath = "yourKeyFile.json";
using (var stream = new FileStream(keyfilepath, FileMode.Open, FileAccess.Read))
{
// As we are using admin SDK, we need to still impersonate user who has admin access
// https://developers.google.com/admin-sdk/directory/v1/guides/delegation
credential = GoogleCredential.FromStream(stream)
.CreateScoped(Scopes).CreateWithUser(EmailOfGoogleDomainAdmin);
}
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ApplicationName",
});
// G Suite User to get information about
// This test user should be suspended
var gs_email = UserToCHeck;
var request = service.Users.Get(gs_email);
var result = request.Execute();
Console.WriteLine("Full Name: {0}", result.Name.FullName);
Console.WriteLine("Email: {0}", result.PrimaryEmail);
Console.WriteLine("ID: {0}", result.Id);
Console.WriteLine("Is Admin: {0}", result.IsAdmin);
Console.WriteLine("Is Suspended: {0}", result.Suspended);
I am writing a web application that needs to access both PowerBI and Microsoft Graph. I am new with OAUTH so I am not understanding how to request access to two different resources. This is my code to access one (PowerBI) resource. How do I modify it to also get access to Microsoft Graph?
class ConfigureAzureOptions : IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly PowerBiOptions _powerBiOptions;
private readonly IDistributedCache _distributedCache;
private readonly AzureADOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureADOptions> azureOptions, IOptions<PowerBiOptions> powerBiOptions, IDistributedCache distributedCache)
{
_azureOptions = azureOptions.Value;
_powerBiOptions = powerBiOptions.Value;
_distributedCache = distributedCache;
}
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = _azureOptions.Instance + "/" + _azureOptions.TenantId;
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
options.ClientSecret = _azureOptions.ClientSecret;
options.Resource = _powerBiOptions.Resource;
// Without overriding the response type (which by default is id_token), the OnAuthorizationCodeReceived event is not called.
// but instead OnTokenValidated event is called. Here we request both so that OnTokenValidated is called first which
// ensures that context.Principal has a non-null value when OnAuthorizeationCodeReceived is called
options.ResponseType = "id_token code";
options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived;
options.Events.OnAuthenticationFailed = OnAuthenticationFailed;
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
string userObjectId = context.Principal.FindFirst(AccessTokenProvider.Identifier)?.Value;
var authContext = new AuthenticationContext(context.Options.Authority, new DistributedTokenCache(_distributedCache, userObjectId));
var credential = new ClientCredential(context.Options.ClientId, context.Options.ClientSecret);
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(context.TokenEndpointRequest.Code,
new Uri(context.TokenEndpointRequest.RedirectUri, UriKind.RelativeOrAbsolute), credential, context.Options.Resource);
context.HandleCodeRedemption(authResult.AccessToken, context.ProtocolMessage.IdToken);
}
private Task OnAuthenticationFailed(AuthenticationFailedContext context)
{
context.HandleResponse();
context.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
You doesn't need to get each access token for different resource at the first sign-in process .
Suppose the first time you are acquiring PowerBI's access token in OnAuthorizationCodeReceived function , in controller , of course you can directly use that access token to call PowerBI's API since token is cached . Now you need to call Microsoft Graph , just try below codes :
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
// Using ADAL.Net, get a bearer token to access the TodoListService
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
result = await authContext.AcquireTokenSilentAsync("https://graph.microsoft.com", credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Just set the resource parameter of AcquireTokenSilentAsync function ,it will use refresh token to acquire access token for new resource .
I had an app that used MSAL and the v2.0 endpoint to sign in users and get token.
I recently changed it to ADAL and the normal AAD endpoint (also changing the app), and now when I try to use the GraphService I get the following error: Current authenticated context is not valid for this request
My user is admin
All permissions have been delegated
The token is successfully retrieved
Here is the code I use:
public static GraphServiceClient GetAuthenticatedClient()
{
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetUserAccessTokenAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return graphClient;
}
Calling the method, where the actual error happens:
try
{
// Initialize the GraphServiceClient.
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient();
// Get events.
items = await eventsService.GetMyEvents(graphClient);
}
catch (ServiceException se)
{
}
Getting the token:
public async Task<string> GetTokenAsync()
{
ClientCredential cc = new ClientCredential(appId, appSecret);
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/tenant.onmicrosoft.com");
AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.microsoft.com", cc);
return result.AccessToken;
}
Can't find anything on this online so I am not sure how to continue.
Error:
This exception is caused by the token acquired using the client credentials flow. In this flow, there is no context for Me.
To fix this issue, you need to specify the whose event you want to get. Or you need to provide the delegate-token.
code for your reference:
//var envens=await graphClient.Me.Events.Request().GetAsync();
var envens = await graphClient.Users["xxx#xxx.onmicrosoft.com"].Events.Request().GetAsync();
The reason there is no info on this is probably because it should be obvious, but I am struggling nonetheless.
After I sign in to my AAD-tenant using ADAL in StartUp.Auth.cs Isuccessfully get a token:
private async Task OnAuthorizationCodeReceivedAAD(AuthorizationCodeReceivedNotification notification)
{
var code = notification.Code;
var credential = new ClientCredential(appId, appSecret);
var userObjectId = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var context = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/tenant.onmicrosoft.com/");
var uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
var result = await context.AcquireTokenByAuthorizationCodeAsync(code, uri, credential);
}
I can add a breakpoint here and view the token. My question is, how do I now access this token through my code from other classes? for example to call an API. The token needs to be delegated, so Client Credentials are not going to work, which is all I can find docs on.
The AuthenticationContext class will store the the token in the cache by default when we acquire the token using it.
Then we can retrieve the token from cache based on the resource and user using AcquireTokenSilentAsync. This method will acquire the token from cache and renew the token if it is necessary. Here is an example for your reference:
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential credential = new ClientCredential(clientId, secret);
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(resource,credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
The tutorial for OneDrive for Business (here: https://dev.onedrive.com/auth/aad_oauth.htm)
However, I don't want the user to have to login as I'm building a web API - I want the app to login. So, I have followed the tutorial for service to service authentication (here: https://msdn.microsoft.com/en-us/library/azure/dn645543.aspx) which gets me an access token.
However, when I try to authenticate with the service I get an error saying "unsupported app only token". The code I'm using is below (btw, I'm using RestSharp):
public string GetAccessToken()
{
var client = new RestClient("https://login.microsoftonline.com/<tenant>/oauth2");
var request = new RestRequest("token", Method.POST);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", <client_id>);
request.AddParameter("client_secret", <client_secert);
request.AddParameter("resource", "https://<tenant>-my.sharepoint.com/");
var response = client.Execute(request);
var content = response.Content;
var authModel = JsonConvert.DeserializeObject<AuthResponseModel>(content);
return authModel.AccessToken;
}
this gets me the access token
This is how I try to access my drive:
public string GetDrive()
{
var accessToken = GetAccessToken();
var client = new RestClient("https://<tenant>-my.sharepoint.com/_api/v2.0/");
var request = new RestRequest("drive", Method.GET);
request.AddHeader("Authorization: Bearer", accessToken);
var response = client.Execute(request);
var content = response.Content;
return content;
}
Does anyone have any tips? This is getting slightly maddening.