Unable to obtain username from WebAPI in ADFS4-OAuth2 OpenID - c#

I am trying to use ADFS Authentication with OAuth to communicate between my webapp and webapi. I am using ADFS4 and have configured application group with Server application and Webapi accordingly. I am trying to receive the userdetails, particularly the username from the webapi controller. Is it possible to pass the username details within the access token passed to webapi. Here is what I did from the Webapp side:
In the webapp controller after adfs authentication,
authContext = new AuthenticationContext(Startup.authority, false);
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
string accessToken = null;
bool isAuthenticated = User.Identity.IsAuthenticated; //return true
string username = User.Identity.Name; // returns username
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value; // returns username
HttpClient httpClient = new HttpClient();
try
{
result = authContext.AcquireTokenAsync(Startup.apiResourceId, credential).Result;
accessToken = result.AccessToken;
}
catch (AdalException ex)
{
}
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = httpClient.GetAsync(Startup.apiResourceId + "/api/ConfApi").Result;
From the Webapi end, in Startup.Auth.cs, I have added these code
public void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
TokenValidationParameters = new TokenValidationParameters() {
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
});
}
However, within the ConfApi controller, I cannot find any claims with user details.
What can I do to receive user details in the Webapi controller?
Thanks for any help.

Are you actually receiving the claims?
Did you configure claims rules for the web API on the ADFS side?
What did you use for Name - Given-Name, Display-Name etc?
Use something like Fiddler to monitor the traffic. After the OIDC authentication, you should see access tokens, id tokens etc.
Take the token and copy into jwt.io.
This will show you what you are actually receiving.
However, the OWIN classes translate the simple OAuth attributes e.g. "aud" into the claim type URI e.g. http://claims/this-claim so breakpoint and see what is in the claims collection and what type has been assigned to each.

The answer to this is the same answer to the question: MSIS9649: Received invalid OAuth request. The 'assertion' parameter value is not a valid access token
You have to use authorization code flow (instead of client credentials grant flow) to get the server app (web app in this case) to talk to the web API with the user's context. Authorization code flow will pass the claims in the JWT Token. Just make sure you pass thru any claims you need for the web API in the web API's RPT claim issuance transform rules.
Vittorio has a nice post on authorization code flow, although it talks about azure.
In order to use authorization code flow, you need to handle the AuthorizationCodeReceived Event via Notifications on the OpenIdConnectAuthenticationOptions from Startup.ConfigureAuth(IAppBuilder app)
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
...
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = async code => {
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
AuthenticationContext authContext = new AuthenticationContext(Startup.authority, false);
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
code.Code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
Startup.apiResourceId);
}
}
When you are ready to make the call you acquire your token silently.
var authContext = new AuthenticationContext(Startup.authority, false);
var credential = new ClientCredential(Startup.clientId, Startup.appKey);
var claim = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userId = new UserIdentifier(claim, UserIdentifierType.UniqueId);
result = await authContext.AcquireTokenSilentAsync(
Startup.apiResourceId,
credential,
userId);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
result.AccessToken);

Related

OpenId Connect and Custom Identity Framework

I'm using the Okta example for implementing OpenIdConnect in an Asp.NET 4.6.x MVC web application. The application uses Unity for Dependency Injection and one of the dependencies is a custom set of classes for the Identity Framework. I'm not using the Okta API because the IdP is not actually Okta and I'm assuming there's proprietary stuff in it. So it's all .NET standard libraries for the OpenId portions.
I can walk through the code after clicking login and it will carry me to the IdP and I can log in with my account, and then it will bring me back and I can see all of the information from them for my login. But it doesn't log me in or anything as it does in the example from Okta's GitHub.
Basically I'm wondering if the identity customization is what's interfering with the login and if there's a way to get in the middle of that and specify what I need it to do?
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
ClientId = clientId
, ClientSecret = clientSecret
, Authority = authority
, RedirectUri = redirectUri
, AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive
, ResponseType = OpenIdConnectResponseType.CodeIdToken
, Scope = OpenIdConnectScope.OpenIdProfile
, PostLogoutRedirectUri = postLogoutRedirectUri
, TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" }
, Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = async n =>
{
//var tokenClient = new TokenClient($"{authority}/oauth2/v1/token", clientId, clientSecret);
var tokenClient = new TokenClient($"{authority}/connect/token", clientId, clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
//var userInfoClient = new UserInfoClient($"{authority}/oauth2/v1/userinfo");
var userInfoClient = new UserInfoClient($"{authority}/connect/userinfo");
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var claims = new List<System.Security.Claims.Claim>();
claims.AddRange(userInfoResponse.Claims);
claims.Add(new System.Security.Claims.Claim("id_token", tokenResponse.IdentityToken));
claims.Add(new System.Security.Claims.Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
claims.Add(new System.Security.Claims.Claim("refresh_token", tokenResponse.RefreshToken));
}
n.AuthenticationTicket.Identity.AddClaims(claims);
return;
}
, RedirectToIdentityProvider = n =>
{
// If signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenClaim != null)
{
n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
}
}
return Task.CompletedTask;
}
}
});
The token(s) returned by Okta have to be managed by your application in order to perform the login action. The OIDC token returned will need to be verified and validated by you, and then a decision made as to whether to accept the OIDC token. If so, you take action to log the user into your application. Recieving an OIDC token as a result of an OpenID Connect flow doesn't by itself log you into an app. The app needs to do some more work based on the token content before taking a login or reject action.

Pass token in header for authentication in MVC and Web API

Integrating MVC app with Web API, Azure Users Authentication is done using OWIN, Want to remove authentication cookie and pass token in header for api call. how to do it? I use MSAL.cs file for Azure AD authentication. Want to pass token in api call header. first load MVC application page, after authentication call web api methods.
I used following code for azure AD autherization,
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
After the first time sign in users from azure ad using the ASP.Net OpenID Connect OWIN middleware , if you want to call web api , you can add the token to request header :
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string authority = String.Format(CultureInfo.InvariantCulture, Startup.aadInstance, tenantID, string.Empty);
ClientCredential credential = new ClientCredential(Startup.clientSecret);
// Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId.
app = new ConfidentialClientApplication(Startup.clientId, redirectUri, credential, new NaiveSessionCache(userObjectID, this.HttpContext)){};
result = await app.AcquireTokenSilentAsync(new string[] { Startup.clientId });
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, serviceUrl + "/api/todolist");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.Token);
HttpResponseMessage response = await client.SendAsync(request);
Please refer to code sample for more details .

Azure AD B2C - Role management [duplicate]

This question already has answers here:
Authorize By Group in Azure Active Directory B2C
(8 answers)
Closed last year.
I have an Asp.NET MVC Application connected with Azure AD B2C.
In the Administrator settings I've created an Administrators Group:
In my code I would like to use [Authorize(Roles = "Administrator")]
With regular Azure Active Directory it was easy to add (just 3 lines of code). But for the Azure AD B2C I cannot find any tutorial or example in the web which is working. Maybe you can tell me what i need to modify.
Here is the ConfigureAuth method of my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}"
}
);
}
Azure AD B2C does not yet include Group claims in the token it sends to the application thus you can't follow the same approach as you outlined with Azure AD (which does include group claims in the token).
You can support this feature ask by voting for it in the Azure AD B2C feedback forum: Get user membership groups in the claims with Azure AD B2C
That being said, you can do some extra work in this application to have it manually retrieve these claims the group claims and inject them into the token.
First, register a separate application that'll call the Microsoft Graph to retrieve the group claims.
Go to https://apps.dev.microsoft.com
Create an app with Application Permissions : Directory.Read.All.
Add an application secret by clicking on Generate new password
Add a Platform and select Web and give it any redirect URI, (e.g. https://yourtenant.onmicrosoft.com/groups)
Consent to this application by navigating to: https://login.microsoftonline.com/YOUR_TENANT.onmicrosoft.com/adminconsent?client_id=YOUR_CLIENT_ID&state=12345&redirect_uri=YOUR_REDIRECT_URI
Then, you'll need to add code the following code inside of the OnAuthorizationCodeReceived handler, right after redeeming the code:
var authority = $"https://login.microsoftonline.com/{Tenant}";
var graphCca = new ConfidentialClientApplication(GraphClientId, authority, GraphRedirectUri, new ClientCredential(GraphClientSecret), userTokenCache, null);
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
try
{
AuthenticationResult authenticationResult = await graphCca.AcquireTokenForClientAsync(scopes);
string token = authenticationResult.AccessToken;
using (var client = new HttpClient())
{
string requestUrl = $"https://graph.microsoft.com/v1.0/users/{signedInUserID}/memberOf?$select=displayName";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
foreach (var group in json["value"])
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, group["displayName"].ToString(), System.Security.Claims.ClaimValueTypes.String, "Graph"));
//TODO: Handle paging.
// https://developer.microsoft.com/en-us/graph/docs/concepts/paging
// If the user is a member of more than 100 groups,
// you'll need to retrieve the next page of results.
}
} catch (Exception ex)
{
//TODO: Handle
throw;
}

Azure B2C Active Directory OpenIDConnect and Authorization Codes

I've set up my web app using OpenIDConnectAuthentication as follows. The OnAuthorizationCodeReceived notification uses Microsoft.IdentityModel.Clients.ActiveDirectory 3.13.8.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
MetadataAddress = Settings.AADB2CAuth.SignInPolicyMetaAddress, // https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration?p={policy} policy = B2C_1_SignIn
AuthenticationType = Settings.AADB2CAuth.SignInPolicyId, // B2C_1_SignIn
ClientId = Settings.AADB2CAuth.ClientId, // {guid}
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived
},
RedirectUri = Settings.AADB2CAuth.RedirectUri,
Scope = "openid",
ResponseType = "id_token",
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential clientCredential = new ClientCredential(Settings.AADB2CAuth.ClientId, Settings.AADB2CAuth.ClientSecret);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(Settings.ClaimTypes.ObjectIdentifier).Value;
string authority = Settings.AADB2CAuth.Authority; // https://login.microsoftonline.com/{tenant}
AuthenticationContext authContext = new AuthenticationContext(authority, new ADAL.ADALTokenCache(userObjectID));
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, redirectUri, clientCredential, Settings.AADGraphApi.GraphResourceId);
}
This works fine. However an authorization code is not returned with the id_token. If change this to code id_token or just code, the AuthorizationCodeReceived notification fires, but then I am met with the error
AADSTS70000: Authentication failed: Authorization Code is malformed or invalid
Basically what I'm trying to do is access the B2C AD as the current signed in user. Is this at all possible?
I updated my Authentication Options to
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = Settings.AADB2CAuth.SignInPolicyId,
Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}", Settings.AADB2CAuth.Tenant, Settings.AADB2CAuth.SignInPolicyId),
ClientId = Settings.AADB2CAuth.ClientId,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived
},
RedirectUri = Settings.AADB2CAuth.RedirectUri,
Scope = "openid",
ResponseType = "code id_token",
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential clientCredential = new ClientCredential(Settings.AADB2CAuth.ClientId, Settings.AADB2CAuth.ClientSecret);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(Settings.ClaimTypes.ObjectIdentifier).Value;
string authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}", Settings.AADB2CAuth.Tenant, Settings.AADB2CAuth.SignInPolicyId);
AuthenticationContext authContext = new AuthenticationContext(authority, new ADAL.ADALTokenCache(userObjectID));
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, redirectUri, clientCredential, Settings.AADGraphApi.GraphResourceId);
}
I am now met with an exception whose details are the HTML content of a 404 page. Looking at the requests I believe it is because AcquireTokenByAuthorizationCodeAsync is looking at https://login.microsoftonline.com/tfp/oauth2/token to send the authorization code to, which I don't think it should?
It may be worth noting that the Authorization Code header I get back is the following:
{
"kid": "cpimcore_09252015",
"ver": "1.0"
}
A quick google search for this yields one result and this references the following issue on the Android ADAL. I'm not sure if this relates to my issue however.
If you look at the beginning of this error:
AADSTSXXXXX
means that when you tried to exchange your auth code, you went to the AAD sts rather than the expected B2C sts:
AADB2CXXXXX
This means your auth code post request was interpreted incorrectly by our endpoint. This is usually caused when the policy (p=B2C_1_xxxx) param for B2C gets appended onto the post URL rather than inside the request.
Option 1:
Refactor your code and library usage to stick the policy param inside the auth code post request rather than the end of the token endpoint URL.
Option 2:
Use the alternate token endpoint and don't tack on any poliy param. Your new endpoint would look like this
https://login.microsoftonline.com/tfp/{tenant}/B2C_1_myB2CPolicy/oauth2/v2.0/token

How to send user token to browser when using OpenId connect with Azure AD?

I am connecting AzureAd with OpenId Connect, I am able to connect to https://login.microsoftonline.com/ with my tenantid, client id and perform the authentication using the below configuration
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
}
},
});
}
My problem is after getting the user token from authorization code i am unable to send it back to the browser. I need to send this token to the browser to perform api calls of the same site or another site registered to the same AD.
I tried to set cookies of both response and request but it didnt work, i cannot see the cookie values in Action.
Is there a way or events available to utilize the token generated from the above sample.
It would be help full if any one can point me to articles describing OpenId connect with AzureAd and Single Sign on.
Thanks in advance,
Mahesh Gupta
Have a look at OpenId protocol specifications. I have already used IdentityServer4 and if I use Implicit Flow, it sets access_token and id_token at response URL, like example.com/api/test#access_token=xxxxxxxx&id_token=xxxxxxxx. Try to append your API response URL in the same way (e.g. using Redirect) or add this data to your response as a new Header.

Categories

Resources