I'm currently trying to find a way to access a web api thru web api using on-behalf-of flow of MSAL.NET
So far, I use the token in the Authorization header to generate the token to be used for the third party group's API
but I'm hoping of finding alternative to this since getting the access token from the header is I think not a healthy approach.
I hope somebody could help me of getting a fresh token that I can use to generate another token with on-behalf-of flow
this is the code that I have:
var cba = ConfidentialClientApplicationBuilder.Create("<our azure client ID>")
.WithAuthority(new Uri("https://login.microsoftonline.com/<Tenant ID>/oauth2/v2.0"))
.WithClientSecret("<Our azure client secret>")
.WithTenantId("<Tenant ID>")
.Build();
AuthenticationResult result = null;
try
{
var authToken = HttpContext.Current.Request.Headers["Authorization"];
var user = new UserAssertion(authToken.Split(' ')[1]);
var scopes = new string[] { "<third party resource client id>/1234.ThirdParty.Api" };
result = await cba.AcquireTokenOnBehalfOf(scopes,user).ExecuteAsync().ConfigureAwait(false);
return new Jwt
{
AccessToken = result.AccessToken,
TokenType = "Bearer",
RefreshToken = null,
ExpiresIn = 3600
};
}
catch (MsalUiRequiredException ex)
{
throw ex;
}
catch (MsalClientException ex)
{
throw ex;
}
Related
I want to implement authentication and authorization using Microsoft.Identity.Client in razor pages
I want to sign In users and logout also with the help of Microsoft office 365
For login I am getting access toke from code and authorizing as shown below
public async Task<IActionResult> OnGetAsync(string code)
{
string clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string tenantId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string clientSecret = "XXXXXXXXXXXXXXXXXX";
var httpClient = new HttpClient();
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.WithRedirectUri("http://localhost:5000/login")
.Build();
var scopes = new string[]
{
"User.Read",
"email",
"offline_access",
"openid",
"profile"
};
try
{
AuthenticationResult result = await app
.AcquireTokenByAuthorizationCode(scopes, code)
.ExecuteAsync();
string identifier = result.Account.HomeAccountId.Identifier;
Email = result.Account.Username;
accesstoken = result.AccessToken;
Console.WriteLine(result.UniqueId);
var graphQlInformation = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/beta/me");
graphQlInformation.Headers.Add("Authorization", "Bearer " + accesstoken);
var information = httpClient.SendAsync(graphQlInformation).Result;
var info = information.Content.ReadAsStringAsync().Result;
JObject json = JObject.Parse(info);
if (information.IsSuccessStatusCode)
{
Console.WriteLine("authorized");
Name = json["mailNickname"].ToString();
Console.WriteLine(json["mailNickname"].ToString());
}
else
{
Console.WriteLine("Un authorized");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return Page();
}
Wit this, I can able to get access token, but I want to keep user session like a cookie, means once a user is signed in,
if he closes browser and open it again, he shouldn't be asked to sign In again
I am following this article until now for authentication and authorization and change some implementation
So, I have 2 questions here
how can I sign in like a session with Oauth 2.0
Thw way I am following for authentication and authorization is right or not.
Please help, and please ask if any more clarification is needed, thanks
Hello I have developed a Microsoft application using Microsoft Graph API in order to obtain planner data and store it in a database for now. On it's own the application works fine without any issue what so ever.
The next task for me is to integrate this separate application into the main company application. The main company's website uses form authentication. What is the best way to integrate this. Currently when I try to login to get authorized I am redirected to the form login not the Microsoft one
I have registered the application in the Microsoft application registration pool. I have also added the office 365 api
This is the token obtain code that i am using
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
tokenCache = new SessionTokenCache(
signedInUserID,
HttpContext.Current.GetOwinContext().Environment["System.Web.HttpContextBase"] as HttpContextBase);
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
tokenCache);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }));
return result.Token;
}
// Unable to retrieve the access token silently.
catch (MsalSilentTokenAcquisitionException)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new Exception(Resource.Error_AuthChallengeNeeded);
}
}
This is the sign in method I am trying use when trying to directly log in
// Signal OWIN to send an authorization request to Azure.
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
I have solved this issue by implementing the following code
public ActionResult SignIn()
{
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
Uri authUri = authContext.GetAuthorizationRequestURL("https://graph.microsoft.com/", SettingsHelper.ClientId,
new Uri(redirectUri), UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
}
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority);
// The same url we specified in the auth code request
string redirectUri = Url.Action("Authorize", "Planner", null, Request.Url.Scheme);
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
try
{
// Get the token
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, new Uri(redirectUri), credential, SettingsHelper.O365UnifiedResource);
// Save the token in the session
Session["access_token"] = authResult.AccessToken;
return Redirect(Url.Action("Index", "Planner", null, Request.Url.Scheme));
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}
A link to the solution that helped tackle this was this. It's slightly old but still helped out massively
https://www.vrdmn.com/2015/05/using-office-365-unified-api-in-aspnet.html
I'm trying to validate the IdToken I get from Azure AD on each request but I keep getting an error saying that there is no signature on the token. When I validate the Access Token it works, but I would rather use the Id Token as that contains the users claims. Is there anyway to make Azure send back the Id Token with the signature as well?
public JwtSecurityToken ValidateJwtToken(string jwtToken)
{
string stsDiscoveryEndpoint = $"{_authority}/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager =
new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
IssuerSigningKeys = config.SigningKeys
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
try
{
SecurityToken token;
tokendHandler.ValidateToken(jwtToken, validationParameters, out token);
return token as JwtSecurityToken;
}
catch (Exception ex)
{
loggingService.LogError("Could not validate azure ad token", nameof(AzureSecurityService), ex);
return null;
}
}
public async Task<string> GenerateToken(string code)
{
AuthenticationContext authenticationContext = new AuthenticationContext(_authority);
try
{
string baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
AuthenticationResult result =
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(code, new Uri(baseUrl), new ClientCredential(_clientId, _clientSecret));
return result.IdToken;
}
catch (AdalException adalex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), adalex);
return null;
}
catch (Exception ex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), ex);
return null;
}
}
public async Task<string> GetAuthUrl()
{
AuthenticationContext authenticationContext = new AuthenticationContext(_authority);
// Config for OAuth client credentials
try
{
string baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
var authUri =
await authenticationContext.GetAuthorizationRequestUrlAsync("00000000-0000-0000-0000-000000000000", _clientId, new Uri(baseUrl), UserIdentifier.AnyUser, null);
return authUri.ToString();
}
catch (AdalException adalex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), adalex);
return null;
}
catch (Exception ex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), ex);
return null;
}
}
UPDATE
So for whatever reason even though there was no scope being provided I was getting back claims. What I have done now is just add query parameters to the auth url for scope and request both openid and profile:
var authUri =
await authenticationContext.GetAuthorizationRequestUrlAsync("00000000-0000-0000-0000-000000000000", _clientId, new Uri(GetBaseUrl()), UserIdentifier.AnyUser, "scope=openid profile");
My question is now why does the default scope not return an id_token with a signature?
You should have forgotten to put required scope value in your authorization request,
From OpenID Connect specification
Scope
REQUIRED. OpenID Connect requests MUST contain the openid scope value.
If the openid scope value is not present, the behavior is entirely
unspecified. Other scope values MAY be present. Scope values used that
are not understood by an implementation SHOULD be ignored. See
Sections 5.4 and 11 for additional scope values defined by this
specification.
So you must specify openid in the authorization request.
And Azure does return an id token for OAuth 2.0 token response (when openid scope is not present).
From Azure AD OAuth2.0 documentation,
id_token
An unsigned JSON Web Token (JWT). The app can base64Url decode the
segments of this token to request information about the user who
signed in. The app can cache the values and display them, but it
should not rely on them for any authorization or security boundaries.
As the documentation say, it's just there to display the end user. One must not use this id token for authenticate/authorize.
On the other hand, for a proper OpenID Connect token response, Auzre sends you a signed id token,
From documentation
id_token
The id_token that the app requested. You can use the id_token to
verify the user's identity and begin a session with the user.
Validate the id_token section of the same documentation explains how to validate the token
Hope this solved your issue.
So I believe my APIservice should be fine since I can return results through Swagger? I am calling from a WPF project. I launch the program and it asks me to login, then it continues and will tell me I don't have permission.
I'm super green to WebAPI2 and think I may just be constructing my call incorrectly. It does seem that I get a token back correctly from my site, the only issue is when I try to actually call on the API for data.
Here is my code:
public static string clientId = "{#Calling App Id}";
public static string commonAuthority = "https://login.windows.net/{#my Azure AD tenant}";
public static Uri returnUri = new Uri("http://MyDirectorySearcherApp");
const string ResourceUri = "https://{#Api App Service}.azurewebsites.net";
public static async Task<List<User>> LoadBands(IPlatformParameters parent)
{
AuthenticationResult authResult = null;
List<User> results = new List<User>();
try {
//get token or use refresh
AuthenticationContext authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.ReadItems().Count() > 0)
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
authResult = await authContext.AcquireTokenAsync(ResourceUri, clientId, returnUri, parent);
} catch (Exception ee) {
throw ex;
}
using (var httpClient = new HttpClient()) {
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"{ResourceUri}/api/Band/")) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
using (var response = await httpClient.SendAsync(request)) {
string responseData = await response.Content.ReadAsStringAsync();
//responseData always equals "You do not have permission to view this directory or page"
return results;
}
}
}
Edit: Maybe helpful to note I'm using a DataAPI that is called by a Rest API, the rest API is secured by Azure AD.
Edit: I'm calling from a Portable Class Library.
Edit: Well, I'm getting authenticated but it does not appear to make any difference. If I completely remove the Auth header I get the same result
It seems that the token is incorrect for the web API which protected by Azure AD. Please check the aud claim in the token which should match the Audience you config in the web API project. You can check the aud claim by parse the token from this site.
And if you still have the problem please share the code how you protect the web API.
Update
If you were using the Express mode like below, you need to acquire the access_token using the app which you associate with the web API.
If you were using the Advanced mode, we should also use the that app to acquire the token and the ResourceUri should matched the value you config in ALLOWED TOKEN AUDIENCES like below:
I'm building two applications, WPF and Web API.
WPF connects to an identity server (now it's Azure AD) and get the access token then send it to my Web API to get the data.
How can I, in Web API, validate the access token to make sure it's correct. ?
Now I'm using Azure as I said but I should build to be able to validate any access token from any identity provider.
Is there an example or article explain this ?
Thanks
Encountered the same probelem.
I decided to use JWTToken
My architecture is the next one
Front <-> WebApi <-> Database
Front is in MVC4 WebApi2
The front will use a FormAuthentication method.
Save the JWT token once the user is fully logged, then send the Authentication Header on each request I do to the webapi.
The front part will only carry the encrypted JWT Token, nothing will be decrypted from it. Only send the token into the authentication http header tag.
On the webapi side each request are cached into DelegatingHandle, Check is the called method need to be authorized or not, validate the JWTToken then do what ever the webapi method does.
I cannot send you some part of my code because this is bellongs now to my company, but I can link you some Internet readings :)
1 - JWT
2- ASP.Net Web API with JWT (webapi handler)
You must then use the [Authorize] or [AllowAnonymous] tags on your webapi methods.
You can even create your own tag to handle all the Groups things.
If you have more questions, feel free to ask :)
I think this will answer to 99% of your security Questions.
I know this is one year old question, but I hope this answer will help other users :)
Client Side code
public async void Authenticate(string aadInstance, string tenant, string clientId, Uri redirectUri, string resourceId)
{
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
authContext = new AuthenticationContext(authority, new FileCache());
AuthenticationResult result = null;
try
{
result = await authContext.AcquireTokenSilentAsync (resourceId, clientId);
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.UserInteractionRequired || ex.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
result = await authContext.AcquireTokenAsync(resourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always));
}
}
ticket = result.AccessToken;
user = result.UserInfo.DisplayableId.Split('#')[0];
}
catch (Exception ex)
{
ticket = "Error";
throw ex;
}
}
Server side code
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
private JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKeys = config.SigningKeys, //.net core calls it "IssuerSigningKeys" and "SigningKeys"
ValidateLifetime = true
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}