Retrieving Claims in OIDC and MVC - owin

I have a controller with the following line of code:
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
In my Startup.Auth.cs, I have the following:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = "",
ClientSecret = "",
Scope = "openid email profile",
ResponseType = "id_token",
Authority = String.Format(CultureInfo.InvariantCulture, IdpInstance, "common", "/v2.0"),
RedirectUri = "https://localhost/abcdeg.ClaimsAdapter.WebApp/Oidc/Authenticate",
CallbackPath = new PathString("/"),
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = OnAuthorization,
SecurityTokenValidated = OnTokenValidate,
AuthenticationFailed = OnAuthenticationFailed,
RedirectToIdentityProvider = OnRedirectToIdentityProvider
},
});
Within the OnTokenValidate, is where I should be getting my claims, I'm assuming. If so, how would I go about it. I know I have a SecurityTokenValidatedNotification, but none of the methods or members of that object seem to expose anything related to Claims. I need to somehow store these claims so that I may retrieve it on my Controller.

Generally for a server side web app, the above Microsoft library will by default just use claims returned in the id token. These claims originate from your Authorization Server.
The place to customize the logic is during the AuthorizationCodeReceived handler. Commonly you will:
Create a new authentication ticket
Customize its claims
The claims will then be serialized to your authentication cookie
Every subsequent request to your app will deserialize the cookie
Common things to do are:
Add the refresh token to your claims
Add other claims from outside the id token
Perhaps add claims that you read from a database or another API
There is some example code here to get you started:
https://github.com/IdentityServer/IdentityServer3/issues/2457

Ah - I see:
DESIGN CONSIDERATIONS
Generally the MS libraries are designed for the server side Authorization Code Flow and you use response type = 'code id_token'
The Implicit Flow instead uses 'token id_token' and I don't think the above libraries support that flow
Also, if you need to call APIs after login you will need to get a 'code' so that you can then get access tokens
UPSHOT: If using the above libraries I would recommend using 'code id_token' - it might fix your problem.
WHAT IS RETURNED OVER THE WIRE?
Can you see what is returned in the id token (via an Online JWT viewer)? This should be where your claims originate from.
IMPLICIT FLOW
This is implemented in Javascript code in the UI rather than in server side code. There is some stuff about this on a tutorial based blog I am writing:
Implicit Flow Messages - see Steps 4 and 7 for login requests + responses

Related

How to refresh Azure AD B2C identity token

I'm using Azure AD B2C with OpenIdConnect for authentication to a web application. I've got it mainly working except that the authentication times out after an hour, even if the user is actively using the application.
It is an old webapp built mostly with ASPX pages. I'm just using an identity token and I'm using cookies. I am not using an access token at all in my app. Access is done a pre-existing way, based on the user claims. I'm using the MSAL.Net library in Microsoft.Identity.Client. Logging in works fine. I get a code back which then gets exchanged for an identity token
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
Everything was working fine, except that the token would expire after 1 hour, no matter what I did. Even if I was using the app, the first request after an hour would be unauthenticated. I tried adding a call to silently acquire a token to see if that would refresh it, but it did not. With OpenIdConnect the offline_access scope is always included. If I try to include it explicitly it throws an error saying so. But I've never seen any evidence that there is a refresh token, even behind the scenes.
I found this question on StackOverflow - Azure AD B2C OpenID Connect Refresh token - and the first answer referenced an OpenIdConnect property called UseTokenLifetime. If I set that to false, then I wouldn't lose authentication after an hour, but the now it was too far the other way. It seemed like the token/cookie would never expire, and I could stay logged in forever.
My desire is that as long as the user is actively using the application, they stay logged in, but if they stop using it for some time (an hour), they have to re-authenticate. I found a way to make that happen through hours of trial and error, I'm just not sure if it makes sense and/or is secure. What I'm doing now is that on each authenticated request, I update the "exp" claim of the user (not sure this matters), and then generate a new AuthenticationResponseGrant, setting the ExpiresUtc to the new time. In my testing, if I hit this code in less than an hour, it keeps me logged in, and then if I wait beyond an hour, I'm no longer authenticated.
HttpContext.Current.User.SetExpirationClaim(DateTime.Now.AddMinutes(60.0));
public static void SetExpirationClaim(this IPrincipal currentPrincipal, DateTime expiration)
{
System.Diagnostics.Debug.WriteLine("Setting claims expiration to {0}", expiration);
int seconds = (int)expiration.Subtract(epoch).TotalSeconds;
currentPrincipal.AddUpdateClaim("exp", seconds.ToString(), expiration);
}
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value, DateTime expiration)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;
// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add new claim
identity.AddClaim(new Claim(key, value));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity),
new AuthenticationProperties() {
IsPersistent = true,
ExpiresUtc = new DateTimeOffset(expiration).UtcDateTime,
IssuedUtc = new DateTimeOffset(DateTime.Now).UtcDateTime
});
}
My question is, does this make sense? Is there any downside? I've never seen any suggestions to do it this way, but it was the only thing I found that worked. If there is a better way to do it, I'd like to know what it is. I considered making my current code an "answer" instead of including it in the question, but I'm not confident that it is correct.
To refresh ID token, you need to use refresh token. Refresh token is opaque to client, but could be cached by MSAL. Then when ID token is expired, MSAL will use the cached refresh token to get a new ID token.
However, you need to implement the cache logic by yourself like instructed in official sample.
Core code snipet:
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
}
catch (Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Unable to get authorization code {ex.Message}."
});
}
}

Authenticate App Services backend using Microsoft Graph token?

Edit:
I have added the "id_token" but still get an "Unauthorized" response.
Here is my login code:
PublicClientApplication myApp = new PublicClientApplication("My-AppID-From-App-Registration-Portal");
string[] scopes = new string[] { "User.Read" };
AuthenticationResult authenticationResult = await myApp.AcquireTokenAsync(scopes).ConfigureAwait(false);
JObject payload = new JObject();
payload["access_token"] = authenticationResult.AccessToken;
payload["id_token"] = authenticationResult.IdToken;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, payload);
Original Post:
Is it possible to authenticate to a App Services backend using the token retrieved from Microsoft Graph?
I have already tried using this token and calling LoginAsync() with AzureActiveDirectory as the provider, this doesn't work.
JObject payload = new JObject();
payload["access_token"] = GraphAuthenticationHelper.TokenForUser;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Is this possible?
UPDATE: In my original answer, I said you cannot do this. But in reality, you can do this but it's a dangerous thing to do since anyone with a valid Microsoft Graph token could theoretically access your APIs. Before I walk you down that path, let me describe the "right" way to access the Microsoft Graph on behalf of your end user.
The right way to do this is to use the on-behalf-of flow in the mobile backend code to exchange the user's ID token for a Microsoft Graph token. The flow looks like the following:
Client initiates a login with AAD using MSAL and sets the resource to the mobile backend (not the Graph). The result should be a set of tokens.
Client uses the mobile SDK to do a login with BOTH the access_token AND the id_token from #1.
Example code:
JObject payload = new JObject();
payload["access_token"] = {access_token.from.msal};
payload["id_token"] = {id_token.from.msal};
var user = await MobileService.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
payload);
The backend code exchanges the user's ID token (from the x-ms-token-aad-id-token request header) for a graph token. This token exchange is known as "on-behalf-of" and is documented here. I think this can be done using ADAL or MSAL libraries, but I wasn't able to find documentation. It's also simple enough that you could implement the HTTP protocol directly without too much trouble.
The backend uses the newly acquired MS Graph token and makes the graph API call.
You can also cache the graph token that you acquire on the backend so that each API call doesn't require more AAD API calls to do token exchange.
I think no ,please refer to document : https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-dotnet-how-to-use-client-library#a-nameauthenticationaauthenticate-users
Replace INSERT-RESOURCE-ID-HERE with the client ID for your mobile app backend. You can obtain the client ID from the Advanced tab under Azure Active Directory Settings in the portal.
The audience of the access token should be the client ID for your mobile app backend . So if resource is https://graph.microsoft.com/(aud claim in access token) , then Client-managed authentication won't work .

Using Oauth to protect WebAPI with Azure active directory

I have browsed all the tutorials regarding using Oauth to protect WebAPI in Azure active directory online. But unfortunately, none of them can work.
I am using VS 2017 and my project is .net core.
So far what I have tried is:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
ervices.AddAuthentication(); // -----------> newly added
}
In "Configure", I added:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
Here is my config:
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "tenantname.onmicrosoft.com",
"Audience": "https://tenantname.onmicrosoft.com/webapiservice"
}
I have registered this "webapiservice" (link is: http://webapiservice.azurewebsites.net) on my AAD.
Also, to access this web api service, I created a webapi client "webapiclient" which is also a web api and also registered it on my AAD and requested permission to access "webapiservice". The webapi client link is: http://webapiclient.azurewebsites.net
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://webapiservice.azurewebsites.net/");
//is this uri correct? should it be the link of webapi service or the one of webapi client?
HttpResponseMessage response = client.GetAsync("api/values").Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsAsync<IEnumerable<string>>().Result;
return result;
}
else
{
return new string[] { "Something wrong" };
}
So theoretically, I should receive the correct results from webapiservice. but I always received "Something wrong".
Am I missing anything here?
You need an access token from Azure AD.
There are plenty of good example apps on GitHub, here is one for a Daemon App: https://github.com/Azure-Samples/active-directory-dotnet-daemon/blob/master/TodoListDaemon/Program.cs#L96
AuthenticationResult authResult = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
This app fetches an access token with its client id and client secret for an API. You can follow a similar approach in your case. You can just replace todoListResourceId with "https://graph.windows.net/" for Azure AD Graph API, or "https://graph.microsoft.com/" for Microsoft Graph API, for example. That is the identifier for the API that you want a token for.
This is the way it works in AAD. You want access to an API, you ask for that access from AAD. In a successful response you will get back an access token, that you must attach to the HTTP call as a header:
Authorization: Bearer accesstokengoeshere......
Now if you are building a web application, you may instead want to do it a bit differently, as you are now accessing the API as the client app, not the user. If you want to make a delegated call, then you will need to use e.g. the Authorization Code flow, where you show the user a browser, redirect them to the right address, and they get sent back to your app for login.
To call web api protected by azure ad , you should pass this obtained access token in the authorization header using a bearer scheme :
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

ServiceStack OAuth2 mobile native authentication

I need to log on through OAuth 2 how can I do that without using WebView in Android?
Thanks.
In the latest v4.5.7 of ServiceStack you'll be able to login into Twitter, Facebook or Github using their SDKs and previous saved access tokens.
Authentication via AccessToken is also made available to OAuth2 providers in the same way where you can authenticate directly by adding the AccessToken to the Authenticate Request DTO, e.g:
var request = new Authenticate
{
provider = "GoogleOAuth",
AccessToken = GoogleOAuthAccessToken,
};
var response = client.Post(request);
response.PrintDump();
Although you will first need to retrieve the AccessToken which typically requires opening a WebView to capture Users consent.
For other OAuth2 providers other than Google Auth you will need to provide an implementation of VerifyAccessToken that returns a boolean that determines whether the AccessToken is valid or not, e.g:
new MyOAuth2Provider {
VerifyAccessToken = accessToken => MyValidate(ConsumerKey,accessToken),
}
This is different for each OAuth provider where some don't provide an API that lets you determine whether the AccessToken is valid with your App or not.

Custom authorization with Azure AD Authentication in OWIN Web API

We are using Azure AD authentication for one of our client application. We want to implement claims based authorization along with it.
Our application set up is Angular Based client app connecting with Web API (both client server secured using Azure AD Bearer Authentication). Server application is hosted using OWIN.
We need to provide custom authorization on server side. There is a provision in Azure AD for adding users and roles. However, that is not enough for us. Our user management is through AD & Security Groups. To gain access to application, users need to part of a base group and further rights (access particular section of application, edit a specific entity etc.) are assigned based on additional groups or given directly to users in the application. Essentially, not all users will be registered in the application and we may have to query the AD using graph API to check which all application specific groups they belong.
OWIN authentication and authorization model is based on Authentication Server and Resource server. We can separate them on need basis. However, in our case, we need to split the authentication and authorization. When the client presents the bearer token, we need to verify if the token is valid and then add claims to user profile. We also need to cache the user claims so that we do not hit the database frequently. (Our client app make multiple Web API calls in one user action.)
What is the location in Identity 2.0 where
I can verify the token &
insert application specific claims
If my entire application revolves around the user authorization and all queries need to be filtered on what data the user can access, which is a more suitable design pattern for the Web API application?
I believe what you're looking for are the Authentication and Authorization filters in the ASP.NET Web API 2.0 stack.
You can implement per-web method authorization by implementing System.Web.Http.Filters.IAuthorizationFilter on an attribute class, then decorate the web action methods of your service controller with that attribute. Web API 2.0 will select a method based on URL routing, notice that there is an attribute on that method implementing IAuthorizationFilter, and will call the ExecuteAuthorizationFilterAsync method on that attribute instance before calling the web method. Placing the authorization step before the web method invocation allows invalid requests to be discarded quickly, before getting into the heavy lifting of parameter binding.
The incoming token is validated by an IAuthenticationFilter implementation which executes before the authorization step.
Documentation and examples are extremely hard to find. Here's one of the few search results that are actually relevant: http://thegrumpycoder.com/post/105427070626/secure-web-services-with-web-api-and-sitecore
you can check if this helps...
UserProfile profile = new UserProfile(); //To deserialize the response stream (JSON)
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
AuthenticationResult result = null;
try
{
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
//use ClientID, ClientSecret
ClientCredential credential = new ClientCredential("b557ceed-xxxx-xxxx-xxxx-xxxxxxxbc240", "AXFxx//xxxxxxxxxxxxxjVFz4sqYm8NDAPEOLkU=");
result = authContext.AcquireTokenSilent("https://graph.windows.net", credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// AcquireTokenSilent may throw exception if the cache is empty. In that case, logout the user and make him login.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
"https://graph.windows.net/cdmsdev.onmicrosoft.com/groups/b40xxxx-14a8-xxxx-9559-xxxxxxca90c8/members/?api-version=1.6");
//Above grap API url is for getting list of users who belong to a specific group (with GUID b40xxxx-1....)
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var upn = ClaimsPrincipal.Current.Identity.Name;
string responseString = response.Content.ReadAsStringAsync().Result;
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
if (profile.Users.Contains(upn)) //check if the current user is in the list of users of the Admin group
return true;
}
}
catch (Exception e)
{
//handle authorization exception here
}
The graph API URL can be replaced with a function to check for membership of a specific group which will directly return a bool value instead of getting all users of that group.

Resources