Update claims after login with identityserver3 2.1.1 - thinktecture-ident-server

We need to update users claims after they log in to our website. This is caused by changes in the users licenses done by another part of our system.
However I am not able to comprehend how to update the claims without logout/login.
Rigth now this is our client setup
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
//user validation host
Authority = UrlConstants.BaseAddress,
//Client that the user is validating against
ClientId = guid,//if not convertet to Gui the compare from the server fails
RedirectUri = UrlConstants.RedirectUrl,
PostLogoutRedirectUri = UrlConstants.RedirectUrl,
ResponseType = "code id_token token",
Scope = "openid profile email roles licens umbraco_api umbracoaccess",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
_logger.Info("ConfigureAuth", "Token valdidated");
var id = n.AuthenticationTicket.Identity;
var nid = new ClaimsIdentity(
id.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var uri = new Uri(n.Options.Authority + "/connect/userinfo");
var userInfoClient = new UserInfoClient(uri,n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
_logger.Info("ConfigureAuth", "AuthenticationTicket created");
},
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
_logger.Debug("ConfigureAuth", "id_token for logout set on request");
_logger.Debug("ConfigureAuth", "Old PostLogoutRedirectUri: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString());
n.ProtocolMessage.IdTokenHint = idTokenHint;
var urlReferrer = HttpContext.Current.Request.UrlReferrer.ToString();
if (!urlReferrer.Contains("localhost"))
{
n.ProtocolMessage.PostLogoutRedirectUri = GetRedirectUrl();
}
else
{
n.ProtocolMessage.PostLogoutRedirectUri = urlReferrer;
}
_logger.Debug("ConfigureAuth", string.Format("Setting PostLogoutRedirectUri to: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString()));
}
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
n.ProtocolMessage.RedirectUri = GetRedirectUrl2();
n.ProtocolMessage.AcrValues = GetCurrentUmbracoId();
_logger.Debug("ConfigureAuth", string.Format("Setting RedirectUri to: {0}", n.ProtocolMessage.RedirectUri.ToString()));
}
},
}
});
We get our custom claims in SecurityTokenValidated
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
I do not follow how to get this without doing a login? Any help is highly appreciated.

That's a reminder that you should not put claims into tokens that might change during the lifetime of the session.
That said - you can set a new cookie at any point in time.
Reach into the OWIN authentication manager and call the SignIn method. Pass the claims identity that you want to serialize into the cookie.
e.g.
Request.GetOwinContext().Authentication.SignIn(newIdentity);

Related

Looping custom claims on login - Asp.net core

Our ASP.NET MVC application connects to IdentityServer 3 with the following config and able to access all the custom claims
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = IdentityServerUrl,
ClientId = IdentityClientId,
ResponseType = "id_token token",
Scope = "openid profile myScope",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var newIdentity = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
"name",
"myrole");
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo"),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => newIdentity.AddClaim(new Claim(ui.Item1, ui.Item2)));
var sid = n.AuthenticationTicket.Identity.Claims.FirstOrDefault(x => x.Type == "sid");
if (sid != null)
{
newIdentity.AddClaim(new Claim("sid", sid.Value));
}
n.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
n.AuthenticationTicket.Properties);
}
}
});
Now we want to upgrade and connect to IdentityServer 3 with .net core
We tried below code but I am not getting the sure how to loop through all the custom claims
.AddOpenIdConnect("oidc", options =>
{
options.Authority = IdentityClientUrl;
options.ClientId = IdentityClientId;
options.ResponseType = OpenIdConnectResponseType.IdTokenToken;
options.Scope.Clear();
options.Scope.Add("profile");
options.Scope.Add("openid");
options.Scope.Add("email");
options.Scope.Add("myScope");
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "myrole"
};
options.SaveTokens = true;
options.ClaimActions.MapUniqueJsonKey("myrole", "myrole", "string");
});
In the existing approach, i am able to get all the claims from userInfo, so I can loop and add everything.
In the asp.net core - however I can map them using ClaimActions, eachone at a time. Is there any way I can loop throug all of them and add all of them - say I don't know the claimType!
Any help please?
You can try this to map all the claims using the MapAllExcept method, like:
options.ClaimActions.MapAllExcept("iss", "nbf", "exp", "aud", "nonce");

WebForms app using OWIN/MSAL not receiving Authorization Code

I have been tasked with modifying several WebForms apps by migrating them to MSAL v4. I have downloaded a working MVC C# example (msgraph-training-aspnetmvcapp) from GitHub and it runs flawlessly. I have managed to emulate the MVC example up to the point of initial token caching. The OWIN single-tenant sign-in process executes as expected; however, the Task assigned to handle notification receipt (OnAuthorizationCodeReceivedAsync) is never fired. Consequently, there is no token placed into the Session cache.
The OWIN middleware is instantiated at startup as follows:
Public Sub ConfigureAuth(ByVal app As IAppBuilder)
System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - STARTED" & vbLf)
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
app.UseCookieAuthentication(New CookieAuthenticationOptions())
app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
.ClientId = appId,
.Scope = $"openid email profile offline_access {graphScopes}",
.Authority = sAuthority,
.RedirectUri = redirectUri,
.PostLogoutRedirectUri = redirectUri,
.TokenValidationParameters = New TokenValidationParameters With {
.ValidateIssuer = False
},
.Notifications = New OpenIdConnectAuthenticationNotifications With {
.AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
.AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync
}
})
System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - COMPLETED" & vbLf)
End Sub
Note that a pair of Notifications have been configured by OWIN, one to indicate successful Authorization Code acquisition (AuthorizationCodeReceived) and another to indicate authentication failure (AuthenticationFailed). Each is mapped to a corresponding asynchronous Task object. The Tasks are defined as follows:
Private Shared Function OnAuthenticationFailedAsync(ByVal notification As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthenticationFailedAsync()" & vbLf)
notification.HandleResponse()
Dim redirect As String = $"~/Views/ErrorPage?message={notification.Exception.Message}"
If notification.ProtocolMessage IsNot Nothing AndAlso Not String.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription) Then
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}"
End If
notification.Response.Redirect(redirect)
Return Task.FromResult(0)
End Function
Private Async Function OnAuthorizationCodeReceivedAsync(ByVal notification As AuthorizationCodeReceivedNotification) As Task
System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthorizationCodeReceivedAsync()" & vbLf)
Dim signedInUser = New ClaimsPrincipal(notification.AuthenticationTicket.Identity)
Dim idClient As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId).WithRedirectUri(redirectUri).WithClientSecret(appSecret).Build()
Dim tokenStore As SessionTokenStore = New SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser)
Try
Dim scopes As String() = graphScopes.Split(" "c)
Dim authResult = Await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync()
Dim userDetails = Await Helpers.GraphHelper.GetUserDetailsAsync(authResult.AccessToken)
Dim cachedUser = New CachedUser() With {
.DisplayName = userDetails.DisplayName,
.Email = If(String.IsNullOrEmpty(userDetails.Mail), userDetails.UserPrincipalName, userDetails.Mail),
.Avatar = String.Empty,
.CompanyName = userDetails.CompanyName
}
tokenStore.SaveUserDetails(cachedUser)
Catch ex As MsalException
Dim message As String = "AcquireTokenByAuthorizationCodeAsync threw an exception"
notification.HandleResponse()
notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
Catch ex As Microsoft.Graph.ServiceException
Dim message As String = "GetUserDetailsAsync threw an exception"
notification.HandleResponse()
notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
End Try
End Function
User sign-in is initiated as follows:
Public Shared Sub SignIn()
System.Diagnostics.Debug.WriteLine("AccountController.vb SignIn()")
If Not HttpContext.Current.Request.IsAuthenticated Then
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(New AuthenticationProperties With {
.RedirectUri = "/"
}, OpenIdConnectAuthenticationDefaults.AuthenticationType)
End If
End Sub
I am not receiving any runtime error messages. There are no build errors or warnings. The app simply hangs once OWIN has taken care of the login process.
In a nutshell, I am trying to understand why program flow is not being passed from the GetOwinContext().Authentication.Challenge() method to the OnAuthorizationCodeReceivedAsync() Task. I have verified from the working MVC example this is the expected behavior.
EDIT:
After tracing both the MVC/C# and WebForms/VB.NET versions of the app, a side-by-side comparison of the two indicates that the WebForms version of the app hangs at the UseOpenIdConnectAuthentication() method. The associated OpenIdConnectAuthenticationNotifications were expanded to include all six available options.
From MVC/C# Startup.Auth.cs:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Scope = $"openid email profile offline_access {graphScopes}",
Authority = "https://login.microsoftonline.com/common/v2.0",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
RedirectToIdentityProvider = (context) =>
{
System.Diagnostics.Debug.WriteLine("*** RedirectToIdentityProvider");
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
System.Diagnostics.Debug.WriteLine("*** MessageReceived");
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
System.Diagnostics.Debug.WriteLine("*** SecurityTokenReceived");
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
System.Diagnostics.Debug.WriteLine("*** SecurityTokenValidated");
return Task.FromResult(0);
}
}
}
);
The following notifications are received:
RedirectToIdentityProvider
MessageReceived
SecurityTokenReceived
SecurityTokenValidated
-- The OnAuthorizationCodeReceivedAsync() method is fired and an access token is retrieved and cached, as expected.
From WebForms/VB.NET Startup.Auth.vb:
app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
.ClientId = appId,
.Scope = $"openid email profile offline_access {graphScopes}",
.Authority = sAuthority,
.RedirectUri = redirectUri,
.PostLogoutRedirectUri = redirectUri,
.TokenValidationParameters = New TokenValidationParameters With {
.ValidateIssuer = False
},
.Notifications = New OpenIdConnectAuthenticationNotifications With {
.AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
.AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync,
.RedirectToIdentityProvider = Function(context)
Debug.WriteLine("*** RedirectToIdentityProvider")
Return Task.FromResult(0)
End Function,
.MessageReceived = Function(context)
Debug.WriteLine("*** MessageReceived")
Return Task.FromResult(0)
End Function,
.SecurityTokenReceived = Function(context)
Debug.WriteLine("*** SecurityTokenReceived")
Return Task.FromResult(0)
End Function,
.SecurityTokenValidated = Function(context)
Debug.WriteLine("*** SecurityTokenValidated")
Return Task.FromResult(0)
End Function
}
})
The following notification is received:
- RedirectToIdentityProvider
-- The application hangs while waiting and no other events are fired.
I am trying to understand why the same OpenID Connect method results in such significantly different behavior between the MVC and WebForms versions of this app.
You are not specifying a response type, so I'm not sure the login is actually working. (Unless MSAL defaults the response_type to id_token, which is possible.)
You should be able to use the Configuration method from this quick start.
Regardless, OpenIdConnect does not typically use the Authorization Code flow for user login. So, the AuthorizationCodeReceived event would not happen.
Now, if your application wants to access a protected resource (Microsoft Graph, SharePoint, AAD-secured WebAPI) after login, then the AuthCode flow is appropriate. This would be the Web app that calls web APIs scenario.

IdentityServer4 client - Refreshing access tokens on CookieAuthenticationEvents

I am trying to use refresh token when the access token expires. A similar so question is answered here. And a sample code to renew token by an action
And i end up with the following code in the startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var logger = loggerFactory.CreateLogger(this.GetType());
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
var httpContextAuthentication = x.HttpContext.Authentication;//Donot use the HttpContext.Authentication to retrieve anything, this cause recursive call to this event
var oldAccessToken = await httpContextAuthentication.GetTokenAsync("access_token");
var oldRefreshToken = await httpContextAuthentication.GetTokenAsync("refresh_token");
logger.LogInformation($"Refresh token :{oldRefreshToken}, old access token:{oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
logger.LogInformation("Refresh token requested. " + tokenResult.ErrorDescription);
if (!tokenResult.IsError)
{
var oldIdToken = await httpContextAuthentication.GetTokenAsync("id_token");
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await httpContextAuthentication.GetAuthenticateInfoAsync("Cookies");
info.Properties.StoreTokens(tokens);
await httpContextAuthentication.SignInAsync("Cookies", info.Principal, info.Properties);
}
x.ShouldRenew = true;
}
else
{
logger.LogInformation("Not expired");
}
}
}
});
The client setup is as follows
AllowAccessTokensViaBrowser = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = 86400,
AccessTokenLifetime = 10,
AllowOfflineAccess = true,
AccessTokenType = AccessTokenType.Reference
After successfully login, i am getting a 401 for every other request. And the log says
[Identity Server]2017-07-04 10:15:58.819 +01:00 [Debug]
"TjpIkvHQi../cfivu6Nql5ADJJlZRuoJV1QI=" found in database: True
[Identity Server]2017-07-04 10:15:58.820 +01:00 [Debug]
"reference_token" grant with value:
"..9e64c1235c6675fcef617914911846fecd72f7b372" found in store, but has
expired.
[Identity Server]2017-07-04 10:15:58.821 +01:00 [Error] Invalid
reference token. "{ \"ValidateLifetime\": true,
\"AccessTokenType\": \"Reference\", \"TokenHandle\":
\"..9e64c1235c6675fcef617914911846fecd72f7b372\" }"
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Token is
invalid.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Creating
introspection response for inactive token.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Information] Success
token introspection. Token status: "inactive", for API name: "api1"
Any help would by highly appreciated
UPDATE:
Basically, when the token expires i get a System.StackOverflowException on the following line
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
UPDATE 2:
Do not use HttpContext.Authentication to retrieve anything. Check my answer below to find the working implementaion
I was working on this for last two days and could not make this work. Funnily, after posting the question here, within 2 hours I make it working :)
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
WriteMessage($"{timeRemaining} and elapsed at {timeElapsed}");
if (timeElapsed > timeRemaining)
{
var oldAccessToken = x.Properties.Items[".Token.access_token"];
var oldRefreshToken = x.Properties.Items[".Token.refresh_token"];
WriteMessage($"Refresh token :{oldRefreshToken}, old access token {oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
if (!tokenResult.IsError)
{
var oldIdToken = x.Properties.Items[".Token.id_token"];//tokenResult.IdentityToken
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
x.Properties.StoreTokens(tokens);
WriteMessage($"oldAccessToken: {oldAccessToken}{Environment.NewLine} and new access token {newAccessToken}");
}
x.ShouldRenew = true;
}
}
}
Basically httpContextAuthentication.GetTokenAsync make this recursive, for that reason StackOverflowException occured.
Please let me know if this implementation has any issue

Missing user_metadata in userInfo of auth0

For authentication I am using Auth0 AuthenticationApi. In Account Controller, I need to fetch the user_metadata but it's missing. Any alternative to fetch the user_metadata?
AuthenticationApiClient client = new AuthenticationApiClient(new Uri($"https://{_auth0Options.Domain}/"));
var authenticateResponse = await client.GetTokenAsync(new ResourceOwnerTokenRequest
{
ClientId = _auth0Options.ClientId,
ClientSecret = _auth0Options.ClientSecret,
Scope = "openid",
Realm = _auth0Options.Connection,
Username = vm.EmailAddress,
Password = vm.Password
});
var user = await client.GetUserInfoAsync(authenticateResponse.AccessToken);
if (user.UserMetadata != null)
{
// Giving error...any alternative to access the userMetaData ?
}
Yes, as far as I see it now, the legacy call still works. However, I don't have a non-legacy solution yet :(
using (var client = GetClient())
{
var jObject = new JObject(new JProperty("id_token", id_token));
var response = await client.PostAsJsonAsync("tokeninfo", jObject);
if (response.IsSuccessStatusCode)
{
var userProfileJson = JObject.Parse(await response.Content.ReadAsStringAsync());
retVal.user_id = userProfileJson.Value<string>("user_id");
retVal.email = userProfileJson.Value<string>("email");
retVal.user_name = userProfileJson.Value<string>("nickname");
if (userProfileJson.Value<string>("created_at") != null)
{
retVal.created_at = userProfileJson.Value<DateTime>("created_at");
}
var exists = userProfileJson.TryGetValue("user_metadata", out JToken meta);

azure active directory graph REST api call through SP.WebRequestInfo on SharePoint Online

Trying to make a REST call through SharePoint's SP.WebRequestInfo.
I'm getting the error "The remote server returned the following error while establishing a connection - 'Unauthorized'." trying to call https://graph.windows.net/[Client]/users?api-version=2013-11-0.
I've successfully retrieved a access token.
Can you help me out why i'm getting this error?
Here is the code i'm using:
var url = "https://graph.windows.net/xxx/users/?api-version=2013-11-08";
var context = SP.ClientContext.get_current();
var request = new SP.WebRequestInfo();
request.set_url(url);
request.set_method("GET");
request.set_headers({
"Authorization": token.token_type + " " + token.access_token,
"Content-Type": "application/json"
});
var response = SP.WebProxy.invoke(context, request);
context.executeQueryAsync(successHandler, errorHandler);
function successHandler() {
if (response.get_statusCode() == 200) {
var responseBody = JSON.parse(response.get_body());
deferred.resolve(responseBody);
} else {
var httpCode = response.get_statusCode();
var httpText = response.get_body();
deferred.reject(httpCode + ": " + httpText);
}
}
The code for retrieving the token is:
this.getToken = function (clientId, clientSecret) {
var deferred = $q.defer();
var resource = "https://graph.windows.net";
var formData = "grant_type=client_credentials&resource=" + encodeURIComponent(resource) + "&client_id=" + encodeURIComponent(clientId) + "&client_secret=" + encodeURIComponent(clientSecret);
var url = "https://login.windows.net/xxxxxx.onmicrosoft.com/oauth2/token?api-version=1.0";
var context = SP.ClientContext.get_current();
var request = new SP.WebRequestInfo();
request.set_url(url);
request.set_method("POST");
request.set_body(formData);
var response = SP.WebProxy.invoke(context, request);
context.executeQueryAsync(successHandler, errorHandler);
function successHandler() {
if (response.get_statusCode() == 200) {
var token = JSON.parse(response.get_body());
deferred.resolve(token);
} else {
var httpCode = response.get_statusCode();
var httpText = response.get_body();
deferred.reject(httpCode + ": " + httpText);
}
}
function errorHandler() {
deferred.reject(response.get_body());
}
return deferred.promise;
};
Erik, something is strange here - you are using the client credential flow from a JavaScript client - this reveals the secret issued to the client app to the user of the JS app.
The client credential flow also requires the directory admin to grant directory read permission to the client application - not sure if this was already configured - nevertheless it must only be used with a confidential client, not a public client like a JS app.
Azure AD does not yet implement the implicit_grant oauth flow using which a JS client app can acquire an access token on behalf of the user over redirect binding (in the fragment). This is a hugh-pro requirement that we're working on - stay tuned.