I am using OWIN OpenID Connect Middleware to connect to Azure AD. I am able to authenticate the user successfully and redirect back to callback endpoint. I am a bit confused here as i am receiving only id_token & code in the response.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Azure AD - TEST",
Caption = "azure AD",
SignInAsAuthenticationType = signInAsType,
ClientId = "some guid",
Authority = "https://sts.windows.net/idp",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = "https://localhost:44392/ExternalLogins/Callback/",
AuthenticationMode = AuthenticationMode.Active,
});
Callback Method :
[HttpPost]
[Route("ExternalLogins/Callback")]
[AllowAnonymous]
public async Task<IHttpActionResult> ExternalLoginCallback()
{
var content = await Request.Content.ReadAsStringAsync();
// I could see the content is a string with id_token, code , state etc.
//id_token is a JWT, so i can decode it and see the user claims and use them later
}
My Questions are :
Is Azure AD used for only authenticating the user ? What about authorizatoin ?
If i want to make calls to other APIs after authentication, how do i do that as i don't have access_token ?
I think i can exchange code with access_token but not sure which Azure endpoint i need to call to get access_token ?
What is the difference between AuthenticationMode.Active and AuthenticationMode.Passive ?
Azure AD can absolutely authorize a user and get your Access/Refresh tokens. It supports all oAuth 2.0 and OIDC flows.
You'll need to get an access token to make calls to an api. Let's say you want to call a /get endpoint on the MS Graph, you will stuff the access token into the body of the http request with the keyword Bearer ey... in front of it.
Additionally, you'll need to go into the Azure Portal and configure the delegated permissions you want to access.
The auth code is used to exchange for the access_token. I suggest checking out this protocol doc that shows you how to use all the endpoints. The short answer is you POST to the /token endpoint.
The difference between active and passive is a bit complex for a SO answer, I recommend reading this blog post about the differences.
I'll just add that if you want to see some sample code using Azure AD you can go to Azure AD Dev Guide or Azure AD code samples on Github.
Related
In this documentation it gives a complete flow for a web application that calls a web API:
The web application executes a policy and the user completes the user experience.
Azure AD B2C returns an (OpenID Connect) id_token and an authorization code to the browser.
The browser posts the id_token and authorization code to the redirect URI.
The web server validates the id_token and sets a session cookie.
The web server asks Azure AD B2C for an access_token by providing it with the authorization code, application client ID, and
client credentials.
The access_token and refresh_token are returned to the web server.
The web API is called with the access_token in an authorization header.
The web API validates the token.
Secure data is returned to the web application.
Looking at 6. and using the code in the Azure-Samples repository active-directory-b2c-dotnet-webapp-and-webapi
, I cannot get the line
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
to return a refresh_token. It returns an IdToken and AccessToken but no RefreshToken.
By using my browser and Postman and following the steps in this document with the same B2C tenant and application I do get the refresh token as expected.
This question is similar to mine and the blog post mentioned in one of the answers provides a work around to the symptom of not having a refresh token but my question remains:
How can I get AcquireTokenByAuthorizationCode to return a refresh_token?
To get refresh token, your application should append offline_access as scope.
You mentioned like this msdn able to return you refresh token. It is because request already contain offline_access scope
&scope=openid%20offline_access
To get refresh token from active-directory-b2c-dotnet-webapp-and-webapi. You need to update Global.cs Scopes filed to include offline_access
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, "offline_access" };
The offline_access scope is optional for web apps. It indicates that your app needs a refresh token for long-lived access to resources.
Go to web.config add below:
<add key ="api:OfflineAccessScope" value="offline_access "/>
And in Global.cs :
public static string OfflineAccessScope = ApiIdentifier + ConfigurationManager.AppSettings["api:OfflineAccessScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, OfflineAccessScope};
Then the Globals.Scopes in AcquireTokenByAuthorizationCode will return refresh token.
UPDATE (solution): I ended up simply extracting the token from the request that my frontend is sending with:
private async Task<string> GetApplicationAccessToken()
{
var token = this.Request
.Headers["Authorization"]
.First()
.Substring("Bearer ".Length);
var assertion = new UserAssertion(token, _ASSERTION_TYPE);
var authResult= await this._app.AcquireTokenOnBehalfOf(new []{""}, assertion)
.ExecuteAsync();
return authResult.AccessToken;
}
ORIGINAL:
I want to funnel data from the MS Graph API (Azure AD endpoint) through my backend (.NET Core Web API) back to my Angular App, that requests the data.
I am running into the problem that I am unable to get an Access token in my backend Web API.
I have Implemented a graph service according to this sample where user consent is prompted through a static html page that is being hosted on the web API. But I want to access MS Graph without explicit user consent.
I have looked for ways to get an access token for my web API without user consent, but not found anything helpful. Only stuff that confuses me. I have also supplied the App registration in Azure AD with application permissions and supplied my web API with sufficient information to the Azure app.
I am still not sure how to exactly adapt the sample code to work with my scenario where user consent is not required / an token already present in the request that my Angular app makes to my web API.
I am getting a userId (objectId.tenantId) in my GraphAuthProvider class when I am trying to call GetAccountAsync(). Yet I still don't receive a token from that call and don't get any error hints, just null.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
var account = await _app.GetAccountAsync(userId);
if (account == null)
{
throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
}
My appsettings.json
"AzureAd": {
"CallbackPath": "/signin-oidc",
"BaseUrl": "https://localhost:63208",
"ClientId": "[redacted]",
"TenantId": "[redacted]",
"ClientSecret": "[redacted]", // This sample uses a password (secret) to authenticate. Production apps should use a certificate.
"Scopes": "user.read profile",
"GraphResourceId": "https://graph.microsoft.com/",
"GraphScopes": "User.Read.All Groups.Read.All"
}
Can you point me in the right direction as to how to call the MS Graph API from my backend by using the application permissions?
Client credential flow using directly http post
In you web api , you can directly create http request to authenticate using client credential flow and retire Microsoft Graph's access token :
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=qWgdYAmab0YSkuL1qKv5bPX
&grant_type=client_credentials
Before that , you'd better admin consent the app permissions , see the detail steps in this article .
Client credential flow using MSAL.NET
If using the MSAL.NET , you can use below code sample for client credential flow :
// Even if this is a console application here, a daemon application is a confidential client application
IConfidentialClientApplication app;
#if !VariationWithCertificateCredentials
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithClientSecret(config.ClientSecret)
.Build();
#else
// Building the client credentials from a certificate
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithCertificate(certificate)
.Build();
#endif
// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch(MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
You can refer to this article and code sample on Github.
Client credential flow using Microsoft Graph .NET authentication library
From document : https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth
You can use Client credential provider :
// Create a client application.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
// Create an authentication provider.
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
// Configure GraphServiceClient with provider.
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
Or directly use MSAL.NET to authenticate using client credential flow and build the Microsoft Graph client like reply from #Philippe Signoret shows .
I am using OpenID Connect to connect to Azure ID, I can successfully authenticate in Azure and get the request coming back to the redirect uri specified in OpenID Azure AD Configuration.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = " TEST",
Caption = "Test Azure AD",
SignInAsAuthenticationType = signInAsType,
ClientId = "<client ID>",
Authority = "https://sts.windows.net/<tenantId>",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = "https://localhost:44392/External/Login", This is another webapi project, not identityserver host.
AuthenticationMode = AuthenticationMode.Passive,
});
After succesful authentication it is redirecting back to https://localhost:44392/External/Login with Code, IdToken.
Questions :
Does it not stop at AuthenticateExternalAsync method on redirection unlike google-signin ?
Do i have to decode IdToken JWT to get user claims?
In the redirection method, how do i generate Access Token from IdSrv3 to authorize other webapis ?
Can a user have both Local Login and Multiple External logins ( Azure AD, Google etc ). In this case how does SSO works with IDsrv3 ?
Is there any IdSrv3 sample with External logins implemented ? Preferably Azure AD ?
I've just struggled through this process, so I'll attempt to answer as best I can to help you/others. Forgive me if I misunderstand your question.
AuthenticateExternalAsync should be called, but you need to have AzureAd return to the IDS (Identity Server) rather than to your App. Your flow should look something like: app -> IDS -> AzureAd -> IDS (AuthenticateExternalAsync) -> App.
In AuthenticateExternalAsync you get the ExternalAuthenticationContext.ExternalIdentity, which contains the claims - no need to decode the JWT token.
IDS handles this once you return a successful AuthenticatedResult in AuthenticateExternalAsync, something like context.AuthenticateResult = new AuthenticateResult("UserId", name, claims);
Yes. You can force the method of logging in as described for SSO purposes, otherwise I imagine IDS would handle it post first-login.
I found this helpful (runs through setup of IDS and AzureAd), but it does use the old Azure Portal rather than the new one. They don't seem to have any samples in their gallery.
Hope that helps a bit :)
I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.
I created an Azure API App and set the [Authorize] attribute on my controller and published it to Azure. Then I registered an Auth0 app, providing the data from my AD app, following Auth0's documentation. When I test the connection on their site it works just fine. I can also log in fine in my app and retrieve the access token with:
var auth0 = new Auth0Client("myUrl", "myTenant");
var user = await auth0.LoginAsync();
var accessToken = user.IdToken;
However, when I make a request to my API, passing the access token in the Authorization header, it just throws an error 401 (Unauthorized). From what the documentation says, I was under the impression this is all that should be needed, and I've not found any information to suggest otherwise. Is there an additional step to linking these?
The Solution is to configure ur API to accept tokens by that issuer, like for example by using owin middleware app.UseJwtBearerAuthentication(). Glad I could help!