I have a .NET MVC Core 3.1 Webapp running on azure. This webapp is with SSO against Azure AD and is consumming powerbi API and graph API in delegated mode.
All was working fine but now I regularly have failed_to_acquire_token_silently Exceptions when AcquireTokenSilentAsync is triggered. This is not 100% of the times and happears to me a bit randomly.
Let me try to extract what I think are the most relevant code parts.
Startup.cs / ConfigureServices:
services.AddAuthentication("Azures").AddPolicyScheme("Azures", "Authorize AzureAd or AzureAdBearer", options =>
{
options.ForwardDefaultSelector = context =>
{
....
};
})
.AddJwtBearer(x =>
{
.....
})
// For browser access
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
Startup.cs / ConfigureTokenHandling:
private void ConfigureTokenHandling(IServiceCollection services)
{
if (Configuration["AuthWithAppSecret:ClientSecret"] != "")
{
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ResponseType = Configuration["AuthWithAppSecret:ResponseType"];
options.ClientSecret = Configuration["AuthWithAppSecret:ClientSecret"];
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
string resource = Configuration["PowerBI:PowerBiResourceUrl"];
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
}
}
A controller is like this :
public class ProjectsController : BaseController
{
private readonly ITokenCacheFactory _tokenCacheFactory;
public ProjectsController(MyContext context, IConfiguration configuration, ITokenCacheFactory tokenCacheFactory)
{
_context = context;
_tokenCacheFactory = tokenCacheFactory;
_configuration = configuration;
}
Later triggered by the controller:
static public async Task<string> GetAccessTokenAsync2(IConfiguration _configuration, ITokenCacheFactory _tokenCacheFactory, ClaimsPrincipal User, string resURL, Uri redirectURI)
{
string authority = _configuration["AzureAd:Authority"];
string clientId = _configuration["AzureAd:ClientId"];
string clientSecret = _configuration["AuthWithAppSecret:ClientSecret"];
var cache = _tokenCacheFactory.CreateForUser(User);
var authContext = new AuthenticationContext(authority, cache);
var credential = new ClientCredential(clientId, clientSecret);
var userId = User.GetObjectId();
AuthenticationResult result;
try
{
result = await authContext.AcquireTokenSilentAsync(
resURL,
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
catch (AdalException ex)
{
mylog.Info("GetAccessTokenAsync - Adal Ex:" + ex.ErrorCode);
if (ex.ErrorCode == "failed_to_acquire_token_silently")
{
// There are no tokens in the cache.
try
{
PlatformParameters param = new PlatformParameters();
result = await authContext.AcquireTokenAsync(resURL, clientId, redirectURI, param, new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
catch (Exception e)
{
mylog.Error("GetAccessTokenAsync - AcquireTokenAsync" + e.ToString());
throw e;
}
}
else
throw ex;
}
return result.AccessToken;
}
AcquireTokenAsync has been added to turn around the failed_to_acquire_token_silently issue (but it is totaly failling).
Do you have any idea why it is failing from time to time ?
Any other idea how to fix it ?
Thanks!!!
Christian
EDIT 07/04:
Here an example:
2021-04-07 15:18:24.674 +00:00 OnAuthorizationCodeReceived is triggered for user fd918ddf-fbb9-40d2-812b-b01876118f42
2021-04-07 15:18:31.675 +00:00 AcquireTokenSilentAsync - trigger exception userId 'fd918ddf-fbb9-40d2-812b-b01876118f42'
The users is authenticated against AD correctly. A code is received and few seconds later there a failed_to_acquire_token_silently exception raised.
The error failed_to_acquire_token_silently occurs when an access token cannot be found in the cache or the access token is expired.
Code sample here:
// STS
string cloud = "https://login.microsoftonline.com";
string tenantId = "331e6716-26e8-4651-b323-2563936b416e";
string authority = $"{cloud}/{tenantId}";
// Application
string clientId = "65b27a1c-693c-44bf-bf92-c49e408ccc70";
Uri redirectUri = new Uri("https://TodoListClient");
// Application ID of the Resource (could also be the Resource URI)
string resource = "eab51d24-076e-44ee-bcf0-c2dce7577a6a";
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult result=null;
try
{
result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently
|| adalException.ErrorCode == AdalError.InteractionRequired)
{
result = await ac.AcquireTokenAsync(resource, clientId, redirectUri,
new PlatformParameters(PromptBehavior.Auto));
}
}
Note that, AcquireTokenSilent does not need to be called in the Client credentials flow (when the application acquires token without a
user, but in its own name)
But you use client credentials flow in your code, you could get access token via AcquireTokenAsync.
clientCredential = new ClientCredential(clientId, appKey);
AuthenticationContext authenticationContext =
new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
AuthenticationResult result =
await authenticationContext.AcquireTokenAsync("https://resourceUrl",
clientCredential);
Related
Note: this is a follow-up of Reusing a Polly retrial policy for multiple Refit endpoints without explicitly managing the HttpClient
When making Refit work with Polly and an Azure AD-based authentication (On Behalf Of flow), I realized that acquiring an OBO token can be very slow (>400ms). The code for acquiring an OBO token based on the current logger in the user access token is shown below:
public async Task<string> GetAccessToken(CancellationToken token)
{
var adSettings = _azureAdOptions.Value;
string[] scopes = new string[] { "https://foo.test.com/access_as_user" };
string? httpAccessToken = _httpContextAccessor.HttpContext?.Request?.Headers[HeaderNames.Authorization]
.ToString()
?.Replace("Bearer ", "");
if (httpAccessToken == null)
throw new ArgumentNullException("Failed to generate access token (OBO flow)");
string cacheKey = "OboToken_" + httpAccessToken;
string oboToken = await _cache.GetOrAddAsync(cacheKey, async () =>
{
IConfidentialClientApplication cca = GetConfidentialClientApplication(adSettings);
var assertion = new UserAssertion(httpAccessToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync(token);
return result.AccessToken;
},
new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(TokenCacheExpirationInMinutes) });
return oboToken;
}
private IConfidentialClientApplication GetConfidentialClientApplication(AzureAdAuthOptions adSettings)
{
var certMetadata = _azureAdOptions.Value.ClientCertificates[0];
string certPath = certMetadata.CertificateDiskPath;
_logger.LogInformation($"GetAccessToken certificate path = {certPath}");
string certPassword = certMetadata.CertificatePassword;
var certificate = new X509Certificate2(certPath, certPassword);
_logger.LogInformation($"GetAccessToken certificate = {certificate}");
var cca = ConfidentialClientApplicationBuilder
.Create(adSettings.ClientId)
.WithTenantId(adSettings.TenantId)
.WithCertificate(certificate)
// .WithClientSecret(adSettings.ClientSecret)
.Build();
return cca;
}
This seems to work fine (not tested in a production environment though). however, I feel that I am reinventing the wheel here as I managing the OBO token caching myself.
Currently, this flow is used by Refit configuration:
private static IServiceCollection ConfigureResilience(this IServiceCollection services)
{
services
.AddRefitClient(typeof(IBarIntegration), (sp) =>
{
var accessTokenHelperService = sp.GetRequiredService<IAccessTokenHelperService>();
return new RefitSettings
{
AuthorizationHeaderValueGetter = () => accessTokenHelperService.GetAccessToken(default)
};
})
.ConfigureHttpClient((sp, client) =>
{
var BarSettings = sp.GetRequiredService<IOptions<BarApiSettings>>();
string baseUrl = BarSettings.Value.BaseUrl;
client.BaseAddress = new Uri(baseUrl);
})
.AddPolicyHandler(Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(x => x.StatusCode is >= HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), RetryPolicyMaxCount)));
return services;
}
Are there any caveats with the current implementation? I am only interested in possible security, performance or "reinventing-the-wheel" issues.
My app requirements is to authenticate using client credentials AND another code (hash).
I followed this link to create and use custom IExtensionGrantValidator.
I manged to invoke the custom IExtensionGrantValidator with approved grant, but client always gets invalid_grant error.
For some reason the set operation ofd Result (property of ExtensionGrantValidationContext) always fails (overriding the Error value returns the overrided value to client).
This is CustomGrantValidator Code:
public class CustomGrantValidator : IExtensionGrantValidator
{
public string GrantType => "grant-name";
public Task ValidateAsync(ExtensionGrantValidationContext context)
{
var hash = context.Request.Raw["hash"]; //extract hash from request
var result = string.IsNullOrEmpty(hash) ?
new GrantValidationResult(TokenRequestErrors.InvalidRequest) :
new GrantValidationResult(hash, GrantType);
context.Result = result
}
}
Startup.cs contains this line:
services.AddTransient<IExtensionGrantValidator, CustomGrantValidator>();
And finally client's code:
var httpClient = new HttpClient() { BaseAddress = new Uri("http://localhost:5000") };
var disco = await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
var cReq = await httpClient.RequestTokenAsync(new TokenRequest
{
GrantType = "grant-name",
Address = disco.TokenEndpoint,
ClientId = clientId,// client Id taken from appsetting.json
ClientSecret = clientSecret, //client secret taken from appsetting.json
Parameters = new Dictionary<string, string> { { "hash", hash } }
});
if (cReq.IsError)
//always getting 'invalid_grant' error
throw InvalidOperationException($"{cReq.Error}: {cReq.ErrorDescription}");
The below codes works on my environment :
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var hash = context.Request.Raw["hash"]; //extract hash from request
var result = string.IsNullOrEmpty(hash) ?
new GrantValidationResult(TokenRequestErrors.InvalidRequest) :
new GrantValidationResult(hash, GrantType);
context.Result = result;
return;
}
Don't forget to register the client to allow the custom grant :
return new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = { "grant-name" },
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
I got the same issue and found the answer from #Sarah Lissachell, turn out that I need to implement the IProfileService. This interface has a method called IsActiveAsync. If you don't implement this method, the answer of ValidateAsync will always be false.
public class IdentityProfileService : IProfileService
{
//This method comes second
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//IsActiveAsync turns out to be true
//Here you add the claims that you want in the access token
var claims = new List<Claim>();
claims.Add(new Claim("ThisIsNotAGoodClaim", "MyCrapClaim"));
context.IssuedClaims = claims;
}
//This method comes first
public async Task IsActiveAsync(IsActiveContext context)
{
bool isActive = false;
/*
Implement some code to determine that the user is actually active
and set isActive to true
*/
context.IsActive = isActive;
}
}
Then you have to add this implementation in your startup page.
public void ConfigureServices(IServiceCollection services)
{
// Some other code
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<Users>()
.AddInMemoryApiResources(config.GetApiResources())
.AddExtensionGrantValidator<CustomGrantValidator>()
.AddProfileService<IdentityProfileService>();
// More code
}
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 have some code that used to call Azure Scheduler to get a token, then using that token, make restful calls. Works a treat.
So i decided to adopt the code into a new app but this time call my own web api hosted on azure. The API is registered in Active directory I have created a secret key etc. When i initiliaze my static httpclient it fetches a token succesfully.
But when i make a call to the API using the token for auth, the response is a 401 "unauthorized", below is the code.
public static class SchedulerHttpClient
{
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
{
string baseAddress = ConfigurationManager.AppSettings["BaseAddress"];
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
await MainAsync(client).ConfigureAwait(false);
return client;
});
public static Task<HttpClient> ClientTask => _Client.Value;
private static async Task MainAsync(HttpClient client)
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string token = await AcquireTokenBySPN(client, tenantId, clientId, clientSecret).ConfigureAwait(false);
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); //TODO ssmith: const or localization
}
private static async Task<string> AcquireTokenBySPN(HttpClient client, string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ConfigurationManager.AppSettings["ARMResource"]),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(client, tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
private static async Task<dynamic> HttpPost(HttpClient client, string tenantId, string payload)
{
var address = String.Format(ConfigurationManager.AppSettings["TokenEndpoint"], tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}//TODO: start removing tests
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
The above code is the class that creates a httpclient and gets its authorization.
public virtual async Task<T> GetAsync(string apiURL)
{
try
{
_client = await SchedulerHttpClient.ClientTask;
var response = await _client.GetAsync(apiURL);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
return responseContent;
}
catch (Exception e)
{
return default(T);
}
}
The above code is a quick lift of my old code simply to test if i can get any results. but as stated it returns a 401.
My question is, is my old code to get authorization incorrect?
<add key="ARMResource" value="https://management.core.windows.net/" />
<add key="TokenEndpoint" value="https://login.windows.net/{0}/oauth2/token" />
<add key="BaseAddress" value="https://mysite.azurewebsites.net" />
As suspected, This particular issue was cause by the incorrect "ARMresource" in the case of a web api it required me to change it to the client id.
Source of answer
Seems my issue was the same, however i suspect i may be able to omit the resource entirely from my SPNPayload string.
Update
some code on web side
startup.cs
app.UseOAuthAuthentication(new OAuthOptions()
{
AuthenticationScheme = "Microsoft-AccessToken",
DisplayName = "MicrosoftAccount-AccessToken",
ClientId = {CliendID},
ClientSecret = {ClientSecret},
CallbackPath = new PathString("/signin-microsoft-token"),
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
UserInformationEndpoint = MicrosoftAccountDefaults.UserInformationEndpoint,
Scope = { "https://graph.microsoft.com/user.read" },
SaveTokens = true,
Events = new OAuthEvents()
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
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 user = JObject.Parse(await response.Content.ReadAsStringAsync());
var identifier = user.Value<string>("id");
if (!string.IsNullOrEmpty(identifier))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.NameIdentifier, identifier,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userName = user.Value<string>("displayName");
if (!string.IsNullOrEmpty(userName))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.Name, userName,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var email = user.Value<string>("userPrincipalName");
if (!string.IsNullOrEmpty(email))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.Email, email,
ClaimValueTypes.Email, context.Options.ClaimsIssuer));
}
}
}
});
HomeController.cs
[Authorize]
public string GetInfo()
{
return "Hello world!";
}
I am able to retrieve user's token with code like this
string MicrosoftClientID = {ClientID};
string MicrosoftCallbackURL = "urn:ietf:wg:oauth:2.0:oob";
string scope = WebUtility.UrlEncode("openid offline_access https://graph.microsoft.com/user.read");
string MicrosoftURL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=" + MicrosoftClientID + "&response_type=code&redirect_uri=" + MicrosoftCallbackURL + "&response_mode=query&scope=" + scope;
Uri StartUri = new Uri(MicrosoftURL);
Uri EndUri = new Uri(MicrosoftCallbackURL);
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
StartUri,
EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
string code = WebAuthenticationResult.ResponseData.Replace("urn:ietf:wg:oauth:2.0:oob?code=", "");
string strContent = "client_id=" + MicrosoftClientID + "&scope=" + scope + "&code=" + code + "&redirect_uri=" + MicrosoftCallbackURL + "&grant_type=authorization_code";
HttpClient httpClient = new HttpClient();
HttpContent httpContent = new StringContent(strContent);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", httpContent);
string stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();
}
but how can i use the token to make a request to API of my .NET Core Web application, which is hosted on azure?
I have tried these
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationModel.AccessToken);
string apicontent = await httpClient.GetStringAsync("https://{host}.azurewebsites.net/home/GetInfo");
apicontent.ToString();
all I got is html of login page
Any help please?
This Authorize is as same as an normal web application.
If you don't want to authenticate the user when you call this API, you can remove the attribure [Authorize] for the GetInfo. And if you also have [Authorize] attribure for the controller, you can add [AllowAnonymous] attribute to specify this action is skipped by AuthorizeAttribute during authorization.
You can refer to here about Authentication and Authorization.
Let's start from the Web Api:
Here is my sample method to retrieve friends list from the Api.
Note that there is "Route prefix" attribute - to indicate which resource I would like to retrieve and "Authorize" attribute to demand authentication before calling this method:
[RoutePrefix("api/Friends")]
public class FriendsController : ApiController
{
[Authorize]
[Route("GetConfirmedFriends")]
public IHttpActionResult GetConfirmedFriends()
{
using (DbWrapper dbContext = new DbWrapper())
{
return Ok(dbContext.GetConfirmedFriends());
}
}
}
Now in the UWP application you will call this method like below:
public async Task<ObservableCollection<Friend>> GetConfirmedFriends()
{
//access token eretrieved after MS authentication:
if (_accessToken == null)
throw new NullReferenceException("Access token cannot be empty. Please authenticate first");
try
{
using (HttpClient client = new HttpClient())
{
ObservableCollection<Friend> friends = null;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _accessToken);
var data = await client.GetAsync(string.Concat(https://{host}.azurewebsites.net/, "api/Friends/GetConfirmedFriends"));
var jsonResponse = await data.Content.ReadAsStringAsync();
if (jsonResponse != null)
friends = JsonConvert.DeserializeObject<ObservableCollection<Friend>>(jsonResponse);
return friends;
}
}
catch (WebException exception)
{
throw new WebException("An error has occurred while calling GetConfirmedFriends method: " + exception.Message);
}
}
Please check and let me know.