Get Azure B2C User Directory listing in Azure Function with MSAL.NET - msal

We are currently getting a list of our Users using MS Graph and the directoryObjects/getByIds endpoint.
In the Startup of the ASP NET Core API we are using Microsoft.IdentityModel.Clients.ActiveDirectory and this code
services.AddHttpClient("GraphApi", async hc =>
{
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/" + this.configuration["GraphApi:Tenant"]);
ClientCredential credential = new ClientCredential(this.configuration["GraphApi:ClientId"], this.configuration["GraphApi:ClientSecret"]);
hc.BaseAddress = new Uri($"https://graph.microsoft.com/v1.0/");
hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credential);
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
});
I am creating a new Azure Function and need to do the same thing again. I was going to use the same code and Microsoft.IdentityModel.Clients.ActiveDirectory but that package has been deprecated and we should be using Microsoft.Identity.Client.
I can see lots of samples for various scenarios but they seem to be all calling the public MS Graph whereas I want to get the users from our own Azure B2C. Can someone point me at the right resources\demo.
The Azure Function will not be running in the context of a user so Managed Identity or Client Secret approach would be useful

I have implemented a similar kind of scenario for getting Azure AD user but different way in MVC
CODE
I have used these NuGet packages
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
Startup class
public class Startup
{
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false // This is a simplification
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
},
}
);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
HomeController
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge( new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut( OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
ClaimsController
public ActionResult Index()
{
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
ViewBag.Name = userClaims?.FindFirst("name")?.Value;
ViewBag.Username = userClaims?.FindFirst("preferred_username")?.Value;
ViewBag.Subject = userClaims?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
ViewBag.TenantId = userClaims?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
return View();
}
I tried to cover all possible implementations. Hope it will work in your case
Thanks

Related

how to validate azure active directory token in webapi

I am trying to validate azure active directory token got in angularapp
i used following code in web api
[HttpGet]
[Route("Validate")]
public 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,
IssuerSigningTokens = config.SigningTokens,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}
the code show error at IssuerSigningTokens = config.SigningTokens, with the message
TokenValidationParameters does not contain definition for
IssuerSigningTokens
Can any one please provide me the solution?
Also i want to return my own token from validate method.
Today,we can use jwtSecurityTokenHandler with System.IdentityModel.Tokens.Jwt.
ATTENTION
System.IdentityModel.Tokens.Jwt version 5.x.x requires .NET Framework 5.x.
If the target framework is .NET Framework 4.5.x or 4.6.x take latest stable 4.x.x version of System.IdentityModel.Tokens.Jwt package.
Validate JWT
public override async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
...
// validate JWT
try
{
await JwtValidator.ValidateJwtToken(token, cancellationToken);
}
catch (SecurityTokenException e)
{
var validationSucceeded = false;
Contract.Assert(validationSucceeded, $"{ErrorCode.UNAUTHORIZED}JWT validation failed ({e.Message}).");
}
...
}
public class JwtValidator
{
private const string STS_DISCOVERY_ENDPOINT_SUFFIX = ".well-known/openid-configuration";
private const string URI_DELIMITER = "/";
public static async Task<SecurityToken> ValidateJwtToken(string token, CancellationToken cancellationToken)
{
var aadInstance = "https://login.microsoftonline.com/{0}";
var tenant = "example.com";
var audience = "853fb202-4201-4e20-97ae-4d5840d9490f";
var authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// Fetch configuration
var stsDiscoveryEndpoint = string.Concat(authority, URI_DELIMITER, STS_DISCOVERY_ENDPOINT_SUFFIX);
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
var config = await configManager.GetConfigurationAsync(cancellationToken);
// extract issuer and token for validation
var issuer = config.Issuer;
var signingTokens = config.SigningTokens.ToList();
// validate token
var validationParameters = CreateTokenValidationParameters(signingTokens, issuer, audience);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out jwt);
return jwt;
}
private static TokenValidationParameters CreateTokenValidationParameters(List<SecurityToken> signingTokens, string issuer, string audience)
{
Contract.Requires(null != signingTokens);
Contract.Requires(!string.IsNullOrWhiteSpace(issuer));
return new TokenValidationParameters()
{
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None,
ValidateLifetime = true
};
}
}
Also i want to return my own token from validate method.
When using OpenIdConnectConfiguration configManager.GetConfigurationAsync(cancellationToken); has to be called in async context by using await (Implies that the surrounding method has to be async).
Otherwise the method call GetConfigurationAsync(cancellationToken) did never return. Even when I tried to run the asynchronous method synchronously by calling .Result or using other mechanisms to run ansynchronous methods synchronous the method didn’t return.
You can see more details about Manual JWT Validation against Azure Active Directory in this blog.

How to store additional data per user like session on Owin using Bearer Token

I need to store a token for thirdy party software calls on my controller after my client sign in, so I tried to save this on User Claims:
public class BaseController : ApiController
{
private const string Token = "thirdyparty.token";
private string Token
{
set
{
// Here I want to store a token in any way (Session, Cache, etc)
var claimsIdentity = (ClaimsIdentity)User.Identity;
var claims = claimsIdentity.Claims;
var tokenClaim = claims.FirstOrDefault(x => x.Type == Token);
if (Token != null)
{
claimsIdentity.RemoveClaim(tokenClaim);
}
claimsIdentity.AddClaim(new Claim(Token, value));
}
get
{
// Here I want to get the token
var claimsIdentity = (ClaimsIdentity)User.Identity;
var claims = claimsIdentity.Claims;
var tokenClaim = claims.FirstOrDefault(x => x.Type == Token);
return tokenClaim?.Value;
}
}
}
This did not work, my new Claim disappeared every time a new request is made.
So, how can I store some additional information per user?
The problem is that the claims are part of the bearer token.
So even if you add the claim to the current identity the next request will
have the old claim values as they are part of the token sent with the new request.
So, if you add a claim you need to generate a new token as well and return that to the client.
One way to generate a new token is to store the OAuthAuthorizationServerOptions, used in the
Startup.cs class, as a static variable and then use that where it's needed
public class Startup
{
public static OAuthAuthorizationServerOptions OAuthServerOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
//....add the rest
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthProvider() //Your derived OAuthAuthorizationServerProvider
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Then to generate a new token
var claimsIdentity = ... //The claim identity after you have added the new claims
var newToken = Startup.OAuthServerOptions.AccessTokenFormat.Protect(new AuthenticationTicket(claimsIdentity, new AuthenticationProperties()));

Why Context.User.Identity.Name is empty from Xamarin?

I can't figure out why when I try to connect from Xamarin Context.User.Indetity.Name is empty. Is there anything special I need to do? I logged in to the server and the user has a connection stablished. After that I use the following code:
var Connection = new HubConnection(Url);
_hub = Connection.CreateHubProxy(hubName);
_hub.On(srvEvent, onData);
await Connection.Start();
But I never get the username. What am I doing wrong?
Here's the code for the server:
var name = Context.User.Identity.Name;
Connections.Add(name, Context.ConnectionId);
return base.OnConnected();
It works when it comes from the web app, not from the xamarin app.
Thanks!
Here is the code I was telling you about.
I'm using an external OAuth2 server for authentication, so I must pass the access token to SignalR somehow, because SignalR uses web sockets for the messages back and forth I can't pass the access token in the header because this is not supported by web sockets.
I'm passing that access token as a query string parameter this way (Javascript client)
$.connection.hub.qs = "access_token=" + mytoken;
Then on my SignalR I added a middleware that takes that query string and adds it to the header as an Authorization header using Bearer Token. This is done this way in my startup class
app.UseAuthQSTokenExtractor();
The code for the middleware is this one
namespace Owin
{
public static class AuthorizationQSTokenExtractorExtension
{
public static void UseAuthQSTokenExtractor(this IAppBuilder app)
{
app.Use<AuthorizationQsTokenExtractorMiddleware>();
}
}
}
namespace Chat.Middleware
{
public class AuthorizationQsTokenExtractorMiddleware : OwinMiddleware
{
public AuthorizationQsTokenExtractorMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
Debug.WriteLine("signalr-auth-middleware");
string bearerToken = context.Request.Query.Get("access_token");
Debug.WriteLine("signar-bearer: " + bearerToken);
if (bearerToken != null)
{
TokenHelper.DecodeAndWrite(bearerToken);
string[] authorization = { "Bearer " + bearerToken };
context.Request.Headers.Add("Authorization", authorization);
}
await Next.Invoke(context);
}
}
My startup class then looks like this
app.UseCors(CorsOptions.AllowAll);
app.UseAuthQSTokenExtractor();
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["api:idserver"],
RequiredScopes = new[]
{
"chat-hub"
}
});
var hubConfiguration = new HubConfiguration ();
hubConfiguration.EnableDetailedErrors = true;
app.RunSignalR(hubConfiguration);
You can see in the code above where I tell SignalR to use the Oauth2 Server, that code is this one
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["api:idserver"],
RequiredScopes = new[]
{
"chat-hub"
}
});
After all this is set up I have access to my Context.User.Identity.Name and if you want to get the others IdentityClaim you can do this
var identity = Context.User.Identity as ClaimsIdentity;
Which I'm using that code above to get the subjectId (userid) like this
public static string[] GetIdentityClaimsIssSub(HubCallerContext Context)
{
var identity = Context.User.Identity as ClaimsIdentity;
if (identity == null)
return null;
var issuerFromIdentity = identity.FindFirst("iss");
var subFromIdentity = identity.FindFirst("sub");
if (issuerFromIdentity == null || subFromIdentity == null)
return null;
return new string[] { issuerFromIdentity.Value, subFromIdentity.Value };
}
I hope it helps

LinqToTwitter Search Never Returns

I am attempting to use LinqToTwitter to search twitter. It works fine as run in an NUnit test but it does not work with ASP.NET or as a WinForm app. I am not sure what Authorizer to use.
public async Task<Search> SearchTwitter(string searchWords)
{
var twitterCtx = BuildTwitterContext();
Task<Search> searchResponse = (from search in twitterCtx.Search
where search.Type == SearchType.Search &&
search.Query == searchWords
select search)
.SingleOrDefaultAsync();
return await searchResponse;
}
private static TwitterContext BuildTwitterContext()
{
IAuthorizer authorizer;
if (HttpContext.Current == null)
authorizer = new PinAuthorizer();
else
authorizer = new AspNetSignInAuthorizer();
InMemoryCredentialStore credentialStore = new InMemoryCredentialStore();
credentialStore.ConsumerKey = consumerKey;
credentialStore.ConsumerSecret = consumerSecret;
credentialStore.OAuthToken = accessToken;
credentialStore.OAuthTokenSecret = accessTokenSecret;
authorizer.CredentialStore = credentialStore;
var twitterCtx = new TwitterContext(authorizer);
return twitterCtx;
}
ASP.NET is different because of the page redirections where you start the authorization and then finish after Twitter redirects back. Here's the LINQ to Twitter documentation that will explain how OAuth works and give you a better idea on which authorizers to use:
https://github.com/JoeMayo/LinqToTwitter/wiki/Learning-to-use-OAuth
The L2T source code also has demos. Here's an OAuth controller demo:
https://github.com/JoeMayo/LinqToTwitter/blob/master/New/Linq2TwitterDemos_Mvc/Controllers/OAuthController.cs
public class OAuthController : AsyncController
{
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> BeginAsync()
{
//var auth = new MvcSignInAuthorizer
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore
{
ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"]
}
};
string twitterCallbackUrl = Request.Url.ToString().Replace("Begin", "Complete");
return await auth.BeginAuthorizationAsync(new Uri(twitterCallbackUrl));
}
public async Task<ActionResult> CompleteAsync()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore()
};
await auth.CompleteAuthorizeAsync(Request.Url);
// This is how you access credentials after authorization.
// The oauthToken and oauthTokenSecret do not expire.
// You can use the userID to associate the credentials with the user.
// You can save credentials any way you want - database,
// isolated storage, etc. - it's up to you.
// You can retrieve and load all 4 credentials on subsequent
// queries to avoid the need to re-authorize.
// When you've loaded all 4 credentials, LINQ to Twitter will let
// you make queries without re-authorizing.
//
//var credentials = auth.CredentialStore;
//string oauthToken = credentials.OAuthToken;
//string oauthTokenSecret = credentials.OAuthTokenSecret;
//string screenName = credentials.ScreenName;
//ulong userID = credentials.UserID;
//
return RedirectToAction("Index", "Home");
}
}
Notice that it uses a WebAuthorizer/SessionStateCredentials pair and separates the start of authorization with a separate action method (specified via callback) for completion.
The following demo shows how to perform OAuth in a WinForms app:
https://github.com/JoeMayo/LinqToTwitter/blob/master/New/Demos/Linq2TwitterDemos_WindowsForms/OAuthForm.cs
public partial class OAuthForm : Form
{
PinAuthorizer pinAuth = new PinAuthorizer();
public OAuthForm()
{
InitializeComponent();
}
async void OAuthForm_Load(object sender, EventArgs e)
{
pinAuth = new PinAuthorizer
{
// Get the ConsumerKey and ConsumerSecret for your app and load them here.
CredentialStore = new InMemoryCredentialStore
{
ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"]
},
// Note: GetPin isn't used here because we've broken the authorization
// process into two parts: begin and complete
GoToTwitterAuthorization = pageLink =>
OAuthWebBrowser.Navigate(new Uri(pageLink, UriKind.Absolute))
};
await pinAuth.BeginAuthorizeAsync();
}
async void SubmitPinButton_Click(object sender, EventArgs e)
{
await pinAuth.CompleteAuthorizeAsync(PinTextBox.Text);
SharedState.Authorizer = pinAuth;
// This is how you access credentials after authorization.
// The oauthToken and oauthTokenSecret do not expire.
// You can use the userID to associate the credentials with the user.
// You can save credentials any way you want - database, isolated storage, etc. - it's up to you.
// You can retrieve and load all 4 credentials on subsequent queries to avoid the need to re-authorize.
// When you've loaded all 4 credentials, LINQ to Twitter will let you make queries without re-authorizing.
//
//var credentials = pinAuth.CredentialStore;
//string oauthToken = credentials.OAuthToken;
//string oauthTokenSecret = credentials.OAuthTokenSecret;
//string screenName = credentials.ScreenName;
//ulong userID = credentials.UserID;
//
Close();
}
}
In this case, you can use a PinAuthorizer with an InMemoryCredentialStore. If you look at that demo, it uses a Web Browser control to navigate to Twitter and manage the OAuth flow.
Look at the URL above for the Learning to use OAuth for examples of other IAuthorizer derived types that you can use in different scenarios. Also, download the source code and step through with the debugger to get a feel for the OAuth workflow.

Json web token in Web api not validating the refresh token after its expiry

I am using JWT authentication for WEB API using OAuth 2. I am using Refresh tokens mechanism. I am able to generate the refresh token and call API service from it before the expiration time. Once the token is expired , i am calling service to issue new token using refresh token id. But its giving error in my CustomJWTFormat class UnProtect method as it not implement any logic. I am not getting what logic to be implemented to reissue JWT refresh token.
Sample codes for configuring serviec to use JSON web token format:
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenFormat = new CustomJwtFormat(<issuer>),
RefreshTokenFormat = new CustomJwtFormat(<issuer>)
};
Sample code of my CustomJWTFormat class:
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private const string AudiencePropertyKey = "as:client_id";
private readonly string _issuer = string.Empty;
private string symmetricKeyAsBase64 = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId))
{
audienceId = <audience>;
symmetricKeyAsBase64 = <secret key>;
}
else
{
using (AuthRepository _repo = new AuthRepository())
{
var audience = _repo.FindClient(audienceId);
symmetricKeyAsBase64 = audience.Secret;
}
}
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
///Need logic for this method. Its calling when service is called to generated new token for refresh id
public AuthenticationTicket Unprotect(string protectedText)
{
throw NotImplementedException();
}
}
}
Any help will be appreciated.
Have a look at this sample to give you an idea of validating a token.
https://github.com/AzureADSamples/WebAPI-ManuallyValidateJwt-DotNet/blob/master/TodoListService-ManualJwt
In particular, Global.asax.cs.

Resources