Azure B2C - 401 unauthorised trying to read id_token in code after logging in - azure-active-directory

I am trying to use the token granted by a secured AAD domain when using my web app
I followed the advice on this link: Retrieve Access Token within a AAD secured Azure Web App
I have managed to get as far as logging in and verifying the ./me URL correctly shows me my token
However when I try and call same token in code I get 401 unauthorised
I have been using the Resource Explorer to configure the additionalLoginParams and have tried to put the app ID as well as the graph URL but nothing has solved the problem
async public Task<string> GetToken()
{
HttpClient _client = new HttpClient();
string _token = "";
HttpResponseMessage response = await _client.GetAsync("https://alfreton.azurewebsites.net/.auth/me");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
ReadUserToken readUserToken = new ReadUserToken();
readUserToken = JsonConvert.DeserializeObject<ReadUserToken>(responseBody);
_token = readUserToken.id_token;
return _token;
}
}
}
EDIT Following the advice below the code now looks like this but I am still getting an Unauthorized error messsage
async public Task<string> GetToken()
{
HttpClient _client = new HttpClient();
string _token = "";
string accessToken = this.Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"];
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await _client.GetAsync("https://alfreton.azurewebsites.net/.auth/me");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
ReadUserToken readUserToken = new ReadUserToken();
readUserToken = JsonConvert.DeserializeObject<ReadUserToken>(responseBody);
_token = readUserToken.id_token;
return _token;
}
When I read through the headers, I find there is X-MS-TOKEN-AAD-ID-TOKEN - can I use that to get an access token?

I agree with #juunas, the URL expects the authentication cookie to be passed in the request when you aceess ./me URL.
The provider-specific tokens are injected into the request header, so you can easily access them. Your provider is AAD, so it should be
string accessToken = this.Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"];

OK I figured it out, what I needed to do is get X-MS-TOKEN-AAD-ID-TOKEN from the Request Headers after logging in then pass that in as the Bearer and that in turn got me a X-MS-TOKEN-AAD-ACCESS-TOKEN which I can use for accessing the API
Thanks loads!

Related

IdentityServer4 Windows Authentication Missing Callback implementation

The documentation to setup Windows Authentication is here: https://docs.identityserver.io/en/latest/topics/windows.html
But I have no idea how to configure the Callback() method referred to in the line RedirectUri = Url.Action("Callback"), or wethere or not I'm even supposed to use that.
I tried manually redirecting back to the https://<client:port>/auth-callback route of my angular app but I get the error:
Error: No state in response
at UserManager.processSigninResponse (oidc-client.js:8308)
Does someone have a suggested Callback method I can use with an SPA using code + pkce ? I've tried searching Google but there are no current example apps using Windows Authentication and the ones that do exist are old.
Take a look at the ExternalLoginCallback method. I've also pasted the version of the code as of 26 Oct 2020 below for future reference incase the repo goes away.
/// <summary>
/// Post processing of external authentication
/// </summary>
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = await AutoProvisionUserAsync(provider, providerUserId, claims);
}
// this allows us to collect any additonal claims or properties
// for the specific prtotocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
additionalLocalClaims.AddRange(claims);
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps);
ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps);
// issue authentication cookie for user
// we must issue the cookie maually, and can't use the SignInManager because
// it doesn't expose an API to issue additional claims from the login workflow
var principal = await _signInManager.CreateUserPrincipalAsync(user);
additionalLocalClaims.AddRange(principal.Claims);
var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id;
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name));
// issue authentication cookie for user
var isuser = new IdentityServerUser(principal.GetSubjectId())
{
DisplayName = name,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// validate return URL and redirect back to authorization endpoint or a local page
var returnUrl = result.Properties.Items["returnUrl"];
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("~/");
}

How to get access token in typed HttpClient

I've got a IdentityServer4/WebAPI setup and a typed HttpClient in my server-side Blazor application. I want to add the access token dynamically when a request to the API is made. But neither the HttpContext nor the AuthenticationStateProvider is accessible in the AddHttpClient method or in the AddHttpMessageHandler.
This works locally but running on the server httpContextAccessor is null:
services.AddHttpClient<IMyClient, MyClient>((serviceProvider, client) =>
{
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}).AddHttpMessageHandler<MyApiBearerTokenHandler>();
Trying to access the AuthenticationStateProvider
services.AddHttpClient<IMyClient, MyClient>((serviceProvider, client) =>
{
var authStateProvider = serviceProvider.GetService<AuthenticationStateProvider>();
}).AddHttpMessageHandler<MyApiBearerTokenHandler>();
throws the following error:
'Cannot resolve scoped service
'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'
from root provider.'
How do I get the access token into AddHttpClient? Am I completely on the wrong track?

Getting Forbidden/Access Denied error from Azure AD Graph Education API

I am using Graph education API, want all information about the user
profile.
Getting below error in response/json objects
Forbidden
AccessDenied
Required claim values are not provided.
public async Task<ActionResult> GetUserDetails()
{
List<User> listUser = new List<User>();
List<UserRole> userRole = new List<UserRole>();
string clientId = configuration.GetValue<string>("AzureAd:ClientId");
string clientSecret = configuration.GetValue<string>("AzureAd:ClientSecret");
//var email = User.Identity.Name;
//AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/LPExamDev.onmicrosoft.com/oauth2/token");
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/LPExamStaging.onmicrosoft.com/oauth2/token");
ClientCredential creds = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
HttpClient http = new HttpClient();
string url = $"https://graph.microsoft.com/v1.0/education/users"; // Microsoft Education Graph
//string url = $"https://graph.microsoft.com/v1.0/users"; // Microsoft Graph // Working fine.
////string url = "https://graph.windows.net/LPExamStaging.onmicrosoft.com/users?api-version=1.6";
// Append the access token for the Graph API to the Authorization header of the request by using the Bearer scheme.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
var jsonResponse = response.ToString();
bool responseCode = response.IsSuccessStatusCode;
//ViewBag.userData = json;
//SaveAPIData(json);
if (responseCode)
{
SaveAPIData(json);
}
}
You need to grant your application EduRoster.Read.All permission and click grant admin consent button.
Login azure portal->click Azure Active Directory->click App registrations(preview)->click your application->click API permissions->add a permission->choose Application permissions
Then click Grant admin consent button.
You can decoded your access token by using https://jwt.io/ to check if you have already got that permission.

o365 calendar API not returning calendar

I am following instructions provided at https://dev.outlook.com/RestGettingStarted/Tutorial/dotnet to get my web application integrated with user's outlook calendar. I am being redirected to login into my microsoft account and it returns to my return url with the code parameter populated. However when I try to generate token out of that code it returns empty without any error.
Can someone please advise?
Following code is to generate token from the code:
// Note the function signature is changed!
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
string authority = "https://login.microsoftonline.com/common";
string clientId = System.Configuration.ConfigurationManager.AppSettings["ida:ClientID"];
string clientSecret = System.Configuration.ConfigurationManager.AppSettings["ida:ClientSecret"];
AuthenticationContext authContext = new AuthenticationContext(authority);
// The same url we specified in the auth code request
Uri redirectUri = new Uri(Url.Action("Authorize", "Home", null, Request.Url.Scheme));
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(clientId, clientSecret);
try
{
// Get the token
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, redirectUri, credential, scopes);
// Save the token in the session
Session["access_token"] = authResult.Token;
// Try to get user info
Session["user_email"] = GetUserEmail(authContext, clientId);
return Content("Access Token: " + authResult.Token);
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}

SignalR authentication failed when passing "Bearer" through query string

I'd like to enable authentication in SignalR while the server was hosted in ASP.NET WebAPI which I'm using OAuth Bearer authrntication and the client is AngularJS.
On client side I originally pass the Bearer token through HTTP header and it works well with the WebAPI. But since SignalR JavsScript doesn't support adding HTTP headers in connection (it's because WebSocket doesn't support specifying HTTP headers) I need to pass the Bearer token through query string by using the code like self.connection.qs = { Bearer: 'xxxxxx' };
The problem is on the WebAPI side my SignalR always returned 401 Unauthorized.
Below is what I did on the WebAPI side.
1, I specified OAuthBearerAuthenticationOptions.Provider to QueryStringEnabledOAuthBearerAuthenticationProvider, which is a class I created inherited from OAuthBearerAuthenticationProvider that can retrieve Bearer token from query string. Code as below.
public class QueryStringEnabledOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
private readonly string _name;
public QueryStringEnabledOAuthBearerAuthenticationProvider()
: this(OAuthDefaults.AuthenticationType)
{
}
public QueryStringEnabledOAuthBearerAuthenticationProvider(string name)
{
_name = name;
}
public override Task RequestToken(OAuthRequestTokenContext context)
{
// try to read token from base class (header) if possible
base.RequestToken(context).Wait();
if (string.IsNullOrWhiteSpace(context.Token))
{
// try to read token from query string
var token = context.Request.Query.Get(_name);
if (!string.IsNullOrWhiteSpace(token))
{
context.Token = token;
}
}
return Task.FromResult(null);
}
}
And registered it as below while WebAPI was started.
var options = new OAuthBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = AuthenticationType,
Provider = new QueryStringEnabledOAuthBearerAuthenticationProvider(),
AccessTokenFormat = _accessTokenFormat,
};
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
app.UseOAuthBearerAuthentication(options);
2, In SignalR part I created an authorize attribute as below. Nothing changed just to be used to add break point.
public class BearerAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return base.AuthorizeHubConnection(hubDescriptor, request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return base.AuthorizeHubMethodInvocation(hubIncomingInvokerContext, appliesToMethod);
}
}
And registered it when WebAPI started as well.
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
EnableJavaScriptProxies = false
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
// Require authentication for all hubs
var authorizer = new BearerAuthorizeAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
});
I found, when SignalR connected my QueryStringEnabledOAuthBearerAuthenticationProvider.RequestToken was invoked and it retrieved Bearer token successfully. But then when SignalR BearerAuthorizeAttribute.AuthorizeHubConnection was invoked the parameter request.User still not authenticated. So it returned 401.
Can anyone give me some ideas on what's wrong I did, thanks.
I'm using headers, this is how I solved it
var authData = localStorageService.get('authorizationData');
var token = authData.token;
$.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };
Hope it helps
I resolved this problem by unprotect the Bearer token from query string in my AuthorizeAttribute, and set the user principal into a new ServerRequest. For detailed information please check http://blog.shaunxu.me/archive/2014/05/27/set-context-user-principal-for-customized-authentication-in-signalr.aspx
This might not be the best solution but it worked.

Resources