I'm currently building a mvc5 app hosted on azure which will be in term used throught a WPF app.
As I need to check user group membership I implemented graph API by following the guidance in this article : https://azure.microsoft.com/fr-fr/documentation/samples/active-directory-dotnet-graphapi-web/
It works quite fine but some time after the user logged in the access to the following controller raise an access denied error :
public async Task<ActionResult> Index()
{
string uID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
IUser adUser = client.Users.Where(u => u.ObjectId == uID).ExecuteAsync().Result.CurrentPage.SingleOrDefault();
IList<Group> groupMembership = new List<Group>();
var userFetcher = (IUserFetcher)adUser;
IPagedCollection<IDirectoryObject> pagedCollection = await userFetcher.MemberOf.ExecuteAsync();
do
{
List<IDirectoryObject> directoryObjects = pagedCollection.CurrentPage.ToList();
foreach (IDirectoryObject directoryObject in directoryObjects)
{
if (directoryObject is Group)
{
var group = directoryObject as Group;
groupMembership.Add(group);
}
}
pagedCollection = await pagedCollection.GetNextPageAsync();
} while (pagedCollection != null);
ViewBag.User = adUser.UserPrincipalName;
ViewBag.UserDN = adUser.DisplayName;
ViewBag.UserGN = adUser.GivenName;
ViewBag.UserMail = adUser.Mail;
ViewBag.UserSN = adUser.Surname;
return View(groupMembership);
}
The exception is raised on GetActiveDirectoryClient(), the code of this method is a strict copy/paste from the article in the link and looks like this :
internal class AuthenticationHelper
{
public static string token;
/// <summary>
/// Async task to acquire token for Application.
/// </summary>
/// <returns>Async Token for application.</returns>
public static async Task<string> AcquireTokenAsync()
{
if (token == null || token.IsEmpty())
{
throw new Exception("Authorization Required. ");
}
return token;
}
/// <summary>
/// Get Active Directory Client for Application.
/// </summary>
/// <returns>ActiveDirectoryClient for Application.</returns>
public static ActiveDirectoryClient GetActiveDirectoryClient()
{
Uri baseServiceUri = new Uri(Constants.ResourceUrl);
ActiveDirectoryClient activeDirectoryClient =
new ActiveDirectoryClient(new Uri(baseServiceUri, Constants.TenantId), async () => await AcquireTokenAsync());
return activeDirectoryClient;
}
}
This code works perfectly right after the user has logged in but after some times the token become null and so the exception is raised.
I'm guessing this is related to some expiration time, so is there's a way to set some auto refresh on the token ?
Thanks !
Thanks for answering, I don't have yet set the [Authorize] tag as I would like to as Azure AD group membership to grant access to controllers and haven't yet figured out how to achieve it :)
It seems that appliying mofifications to the authenticationHelper solved the issue :
public static ActiveDirectoryClient GetActiveDirectoryClient()
{
Uri baseServiceUri = new Uri(Constants.ResourceUrl);
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, Constants.TenantId), async () =>
{
var result = await authContext.AcquireTokenSilentAsync(graphUrl, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
return result.AccessToken;
});
return activeDirectoryClient;
}
I don't know if that's a clean way to do thing it at least it works.
Related
I have built a Blazor Server App with Azure AD authentication. This server app access a web api written in net core and sends the JWT token to that api. Everything is working, data is gathered, page is displayed accordingly.
The problem is: after some time, when user interacts with some menu option in UI, nothing else is returned from webapi. After some tests I found out that the token has expired, then when it is sent to web api, it is not working. But the AuthenticationState remains same, like it is authenticated and valid irrespective the token is expired.
Thus, I have been trying some suggestions like : Client side Blazor authentication token expired on server side. Actually it is the closest solution I got.
But the problem is that, after implemented a CustomAuthenticationStateProvider class, even after injected it, the default AuthenticationStateProvider of the app remains like ServerAuthenticationStateProvider and not the CustomAuthenticationStateProvider I have implemented. This is part of my code:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IConfiguration _configuration;
private readonly ITokenAcquisition _tokenAcquisition;
public CustomAuthenticationStateProvider(IConfiguration configuration, ITokenAcquisition tokenAcquisition)
{
_configuration = configuration;
_tokenAcquisition = tokenAcquisition;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var apiScope = _configuration["DownloadApiStream:Scope"];
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
string savedToken = string.Empty;
try
{
savedToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope });
}
catch (MsalUiRequiredException)
{
savedToken = string.Empty;
}
catch (Exception)
{
savedToken = string.Empty;
}
if (string.IsNullOrWhiteSpace(savedToken))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(savedToken).ToList();
var expiry = claims.Where(claim => claim.Type.Equals("exp")).FirstOrDefault();
if (expiry == null)
return anonymousState;
// The exp field is in Unix time
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiry.Value));
if (datetime.UtcDateTime <= DateTime.UtcNow)
return anonymousState;
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
}
public void NotifyExpiredToken()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
This is my Program.cs where I added the services :
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());
Here in the MainLayou.razor, I inject the service and try to use it :
#inject CustomAuthenticationStateProvider authenticationStateProvider;
protected async override Task OnInitializedAsync()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity == null || !authState.User.Identity.IsAuthenticated)
{
authenticationStateProvider.NotifyExpiredToken();
}
await base.OnInitializedAsync();
}
The problem comes up here, because the authenticationStateProvider is not an instance of the CustomAuthenticationStateProvider , but the instance of ServerAuthenticationStateProvider. It is like AuthenticationStateProvider was not replaced by the custom implementation, therefore I can't use the NotifyAuthenticationStateChanged and inform the CascadingAuthenticationState that it was changed.
If anyone has already been thru this or have any suggestion, it would be appreciated.
Actually I just wanna to change authentication state to not authenticated. So user will be pushed to login again using Azure AD.
Thanks
Question:
I am not sure if this falls under question or code review because the code works where I do not know if it is implemented correctly. But, do we need to acquire the access token from Microsoft.Graph using either silent or interactive modes? From what I can tell the answer is, No. (see Context below)
The new implementation seems to be drastically scaled down with the whole idea of silent and interactive token retrieval being removed. Is this correct?
using Azure.Identity;
using Microsoft.Graph;
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var scopes = new[] { "User.Read" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "SomeGuid";
// Value from app registration
var clientId = "SomeGuid";
var options = new InteractiveBrowserCredentialOptions
{
TenantId = tenantId,
ClientId = clientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
// MUST be http://localhost or http://localhost:PORT
// See https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core
RedirectUri = new Uri("http://localhost:1234"),
};
// https://learn.microsoft.com/dotnet/api/azure.identity.interactivebrowsercredential
var interactiveCredential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(interactiveCredential, scopes);
// Interactive browser login occurs here.
var me = graphClient.Me.Request().GetAsync().Result;
// Printing the results
Console.WriteLine("-------- Data from call to MS Graph --------");
Console.Write(Environment.NewLine);
Console.WriteLine($"Id: {me.Id}");
Console.WriteLine($"Display Name: {me.DisplayName}");
Console.WriteLine($"Email: {me.Mail}");
//Console.ReadLine();
}
}
}
Context:
As part of our routine maintenance, I was tasked with upgrading our NuGet packages on a Winforms desktop application that is running in Azure and whose users are in Azure Active Directory Services (AADS). One of the packages, Microsoft.Graph, had a major version change. https://www.nuget.org/packages/Microsoft.Graph/4.0.0
The documentation on it indicated a new feature for handling the TokenCredentialClass. https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/4.0.0/docs/upgrade-to-v4.md#new-capabilities
From what I can tell, there is a separate and distinct break on how the token is retrieved. Previously, we followed the method provided here: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-windows-desktop#add-the-code-to-initialize-msal
Old way:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
_PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithRedirectUri("http://localhost:1234")
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.Build();
// We sign the user in here
bolIsAutorizeSSO = CallMicrosoftSSO().GetAwaiter().GetResult();
InteractiveAuthenticationProvider = new InteractiveAuthenticationProvider(PublicClientApp, Scopes);
GraphServiceClient = new Microsoft.Graph.GraphServiceClient(InteractiveAuthenticationProvider);
if (bolIsAutorizeSSO)
{
// We also signt the user in here.
var User = GraphServiceClient.Me.Request().GetAsync().Result;
// Printing the results
Console.WriteLine("-------- Data from call to MS Graph --------");
Console.Write(Environment.NewLine);
Console.WriteLine($"Id: {User.Id}");
Console.WriteLine($"Display Name: {User.DisplayName}");
Console.WriteLine($"Email: {User.Mail}");
}
else
{
// signout
Console.ReadLine();
}
}
public static async Task<bool> CallMicrosoftSSO()
{
AuthenticationResult authResult = null;
var app = PublicClientApp;
var accounts = await app.GetAccountsAsync();
try
{
authResult = await app.AcquireTokenInteractive(Scopes)
.WithAccount(accounts.FirstOrDefault())
.WithPrompt(Microsoft.Identity.Client.Prompt.ForceLogin)
.ExecuteAsync();
}
catch (MsalUiRequiredException _Exception)
{
// A MsalUiRequiredException happened on AcquireTokenSilent.
// This indicates you need to call AcquireTokenInteractive to acquire a token.
Console.WriteLine(_Exception.Message);
}
catch (MsalException msalex)
{
if (msalex.ErrorCode != "authentication_canceled")
{
Console.WriteLine(msalex.Message);
}
}
catch (Exception _Exception)
{
Console.WriteLine(_Exception.Message);
}
if (authResult != null)
{
return true;
}
return false;
}
private static string ClientId = "SomeGuid";
private static string TenantId = "SomeGuid";
private static string[] Scopes = new string[] { "User.Read" };
private static Microsoft.Graph.GraphServiceClient GraphServiceClient;
private static bool bolIsAutorizeSSO = false;
private static InteractiveAuthenticationProvider InteractiveAuthenticationProvider;
private static IPublicClientApplication _PublicClientApp;
public static IPublicClientApplication PublicClientApp { get { return _PublicClientApp; } }
}
}
I am struggling to make sense of it. Partly because the feature is brand new and there are very few code samples up on the internet that say do it this way. What I have found seems to point me back to what we already are using (more on that in a bit). So, the examples may not yet be fully updated.
Currently i am trying to implement the azure active directory authentication by passing user name and password. So for this i have trying to get the access toke but facing issue to get the same. If i use the client id and client secret then i am able to get the token but when i try to by passing username and password then its not giving the result and throwing the exception :
"error":"invalid_client","error_description":"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'
Below the code which i am using for this:
/// <summary>
/// Working with client id and client secret
/// </summary>
/// <returns></returns>
public async Task<string> GetTokenUsingClientSecret()
{
//authentication parameters
string clientID = "XXXXXXXXXXXXXXXXXXXXXXXXXX";
string clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX";
string directoryName = "xxx.onmicrosoft.com";
var credential = new ClientCredential(clientID, clientSecret);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/" + directoryName, false);
var result = await authenticationContext.AcquireTokenAsync("https://management.core.windows.net/", clientCredential: credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
/// <summary>
/// Not Working with username and password.
/// </summary>
public async Task<string> GetTokenUsingUserNamePassword()
{
try
{
string user = "username.onmicrosoft.com";
string pass = "yourpassword";
string directoryName = "XXXX.onmicrosoft.com";
string authority = "https://login.microsoftonline.com";
string resource = "https://management.core.windows.net/";
string clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var credentials = new UserPasswordCredential(user, pass);
var authenticationContext = new AuthenticationContext($"{authority}/{directoryName}");
var result = authenticationContext.AcquireTokenAsync(resource: resource, clientId: clientId, userCredential: credentials);
return result.Result.AccessToken;
}
catch (Exception ex)
{
throw ex;
}
}
AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.
According to your mentioned exception, I assume that you registry an Azure AD Web app/API application. Please have a try to use an Azure AD native appilcation then it should work. More details you could refer to this document -Constraints & Limitations section.
No web sites/confidential clients
This is not an ADAL limitation, but an AAD setting. You can only use those flows from a native client. A confidential client, such as a web site, cannot use direct user credentials.
How to reigstry Azure AD native Application.
Summary:
Our Universal Windows App single-tenant client uses an ASP.NET Web API 2 as a proxy for single-sign on for various Microsoft Office 365 APIs. We use Active Directory for server authentication and the on-behalf-of single sign-on model in our server to exchange tokens for the Office 365 APIs.
Problem:
We have updated a permission scope in Azure for the Office 365 API and the user is not prompted to authorize permission for the new scope, nor is the new scope appearing on NEW tokens. What needs to be done to DETECT and ALLOW our users to authorize new permission scopes?
Additional Details:
Our server is hosted in MSFT Azure App Services. I understand the manifest in Azure is auto-generated and does not need to be manually updated to reflect the updated permission scope?
When the user first logs into the UWP app, they consent to single sign-on permissions associated with the server (eg. Mail.ReadWrite, etc.) which works fine. However, the user consent prompt does not show up again, even after I’ve removed both the client and server apps from my list of consented to apps using
We use the WebTokenRequest and WebAuthenticationCoreManager libraries in the client to get the token for the server. I have also tried using WebAuthenticationBroker (which is not the correct method for our sign-on architecture) and the ADAL library in our client. None of these libraries are prompting for the updated permission.
I have also tried adding wtf.Properties.Add("prompt", "consent"); to our WebTokenRequest to force the user to reapprove permissions. This does not work.
I have also tried restarting the App Service in Azure. This does nothing.
UPDATED 11/10/16:
Following is relevant code I've pulled from our app architecture which may help. Additionally, our server utilizes ADAL version 2.24.304111323.
In our UWP app:
public class AppAuth
{
WebTokenRequestResult result;
WebAccount acc;
async Task<WebTokenRequestResult> GetTokenAsync(WebTokenRequestPromptType promptType = WebTokenRequestPromptType.Default)
{
var wtr = new WebTokenRequest(
provider: "https://login.windows.net",
scope: "",
clientId: appClientId,
promptType: promptType
);
wtr.Properties.Add("authority", "https://login.windows.net");
wtr.Properties.Add("resource", azureWebsiteUrl);
if (promptType != WebTokenRequestPromptType.ForceAuthentication)
{
result = (acc == null) ?
await WebAuthenticationCoreManager.GetTokenSilentlyAsync(wtr) :
await WebAuthenticationCoreManager.GetTokenSilentlyAsync(wtr, acc);
}
if (promptType == WebTokenRequestPromptType.ForceAuthentication ||
result?.ResponseStatus == WebTokenRequestStatus.UserInteractionRequired)
{
result = (acc == null) ?
await WebAuthenticationCoreManager.RequestTokenAsync(wtr) :
await WebAuthenticationCoreManager.RequestTokenAsync(wtr, acc);
}
return result;
}
}
In our server:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true,
ValidateIssuer = false,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
});
}
}
public class TokenChange
{
protected AdUser _user;
private UserAssertion _assertion;
private static string _aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string _tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string _clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string _appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private string _accessToken;
public AuthenticationResult AuthResult { get; set; }
public AdalException AuthException { get; set; }
private string _emailAddress;
private HttpClient _httpClient;
public bool Authenticate()
{
_accessToken = null;
if (ClaimsPrincipal.Current.Identity.IsAuthenticated)
{
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext
as System.IdentityModel.Tokens.BootstrapContext;
if (bootstrapContext != null)
{
Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
var upn = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn);
var email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email);
var userName = upn != null ? upn.Value : email?.Value;
_emailAddress = ClaimsPrincipal.Current.Identity.Name;
var userNameClaim = ClaimsPrincipal.Current.FindFirst("name");
_fullName = userNameClaim != null ? userNameClaim.Value : String.Empty;
_accessToken = bootstrapContext.Token;
_assertion = new UserAssertion(_accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
}
}
return _accessToken != null;
}
public bool GetAccess(string apiResource)
{
bool gotAccess = false;
AuthResult = null;
AuthException = null;
if (_accessToken != null || Authenticate())
{
ClientCredential clientCred = new ClientCredential(_clientId, _appKey);
string authority = String.Format(CultureInfo.InvariantCulture, _aadInstance, _tenant);
AuthenticationContext authContext = new AuthenticationContext(authority);
bool retry = false;
int retryCount = 0;
do
{
retry = false;
try
{
AuthResult = authContext.AcquireToken(apiResource, clientCred, _assertion);
}
catch (AdalException ex)
{
AuthException = ex;
if (ex.ErrorCode == "temporarily_unavailable")
{
retry = true;
retryCount++;
Thread.Sleep(500);
}
else
{
throw (ex);
}
}
} while ((retry == true) && (retryCount < 1));
if (AuthResult != null && AuthResult.AccessToken != null)
{
gotAccess = true;
}
}
return gotAccess;
}
Based on the description, you were developing an single tenant application which calling the downstream web API(Office 365 API) in your web API.
If you were using the cache to acquire the token in your web API, it will not acquire the new token unless the token is expired. And in this scenario, there is no need to consent/reconsent to update the permission.
Please ensure that you web API is acquire the token from new request instead of cache. If you were using the DbTokenCache, you can clear the cache by deleting the token cache records in PerWebUserCaches table in the database.
Note
In the describing scenario above, since the downstream web API(Office 365 API) get the token using the token issued for your web API which require users sign-in. So only the delegated permission work in the scenario( scp claim in the token instead of roles).
I am using Azure AD to authenticate the users. I want to add few user claims specific to my application. Should I do it in Application_PostAuthenticateRequest` in global.asax ?. Is there a way I can cache my claims too ?
If you are using the ASP.NET OWIN middleware, there are specific notifications you can use for that purpose. Claims added in that way will end up in your session cookie, so that you won't have to repeat the claims augmentation logic in subsequent calls. See http://www.cloudidentity.com/blog/2015/08/26/augmenting-the-set-of-incoming-claims-with-the-openid-connect-and-oauth2-middleware-in-katana-3-x/ for details.
BTW you can add your custom cliams but you cannot override the existing claims added by the Azure AD (what i have seen so far might be i am wrong). what you can do is to add the new cliams like this
AuthorizationCodeReceived = context =>
{
List<System.Security.Claims.Claim> allcustomClaims = new List<System.Security.Claims.Claim>();
allcustomClaims.Add(new System.Security.Claims.Claim("customClaim", "YourDefindedValue"));
context.AuthenticationTicket.Identity.AddClaims(allcustomClaims);
return Task.FromResult(0);
}`
and then you can get the claim anywhere in controller like
#{
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
if (claimsIdentity != null)
{
var c = claimsIdentity.FindFirst("customClaim").Value;
}
}
You can augment the claims programmatically like this:
public async Task<ActionResult> AuthenticateAsync()
{
ClaimsPrincipal incomingPrincipal = System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal;
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
{
ClaimsIdentity claimsIdentity = incomingPrincipal.Identity as ClaimsIdentity;
if (!claimsIdentity.HasClaim(ClaimTypes.Role, "Admin"))
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "AADGuide"));
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
AuthenticateResult authResult = await authenticationManager.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
authenticationManager.SignIn(authResult.Properties,claimsIdentity);
}
}
return RedirectToAction("Index", "Start");
}
This solution relies on AuthenticationAsync method of AuthenticationManager to retrieve the original AuthenticationProperties. After retrieving the properties, call the SignIn method to persist the new ClaimsIdentity in the auth cookie.
If you're making use of:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
...
This is how I managed to add additional custom claims using new OAuthBearerAuthenticationProvider:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
// The id of the client application that must be registered in Azure AD.
TokenValidationParameters = new TokenValidationParameters { ValidAudience = clientId },
// Our Azure AD tenant (e.g.: contoso.onmicrosoft.com).
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
// In this handler we can perform additional coding tasks...
OnValidateIdentity = async context =>
{
try
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"].First();
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
// Credential representing the current user. We need this to request a token
// that allows our application access to the Azure Graph API.
var userUpnClaim = identity.FindFirst(ClaimTypes.Upn);
var userName = userUpnClaim == null
? identity.FindFirst(ClaimTypes.Email).Value
: userUpnClaim.Value;
var userAssertion = new UserAssertion(
userJwtToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
identity.AddClaim(new Claim(identity.RoleClaimType, "myRole"));
}
catch (Exception e)
{
throw;
}
}
}
});
For a full sample, check this blog post.