I have this fairly straightforward use-case:
Resource owner uses my Angular client to obtain a JWT token from IDP
Angular client calls Service A (WebAPI) with the access token issued by IDP
Angular client calls Service B (WebAPI) with the access token issued by IDP
I would like to support the following scenario:
Have Service A act like the Angular client and pass-through the access token it received to make a call to Service B
So basically, Service B can be called either directly by the Angular client or by Service A. In both cases, it must be provided a Bearer token in order to access any of the WebAPI endpoints.
From Service A, I do not know how to store the provided token so that later on when I need to use the HttpClient to call Service B I can set the Bearer header.
If I understood correctly, your requirement is to call the second API (Service B) as part of a single request to Service A from an authenticated user.
If this is the situation, then I believe there is no reason to store the token server-side, and you may just take the Authorization header from the current request and reuse it to call Service B.
Some code may help explain what I mean, assuming ControllerA is a Service A controller:
public class ControllerA : ApiController
{
public async Task<IHttpActionResult> GetFromB()
{
var token = Request.Headers.Authorization.Parameter;
MyModel result = null;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await client.GetAsync("http://serviceb/controllerb/actionb");
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsAsync<MyModel>();
}
return Ok(result);
}
}
Related
I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.
I'm trying to get azure AD authentication working between a Blazor WASM app, and another API that I have running locally but on a different port. I need both applications to use the Azure login, but I only want the user to have to log in once on the Blazor app which should then pass those credentials through to the API.
I've set up app registrations for both apps in the portal, created the redirect url, exposed the API with a scope and I can successfully log into the blazor app and see my name using #context.User.Identity.Name.
When it then tries to call the API though, I get a 401 error back and it doesn't hit any breakpoints in the API (presumably because there is no authentication being passed across in the http request).
My code in the Blazor app sets up a http client with the base address set to the API:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("APIClient", client => client.BaseAddress = new Uri("https://localhost:11001"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("APIClient"));
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://d3152e51-9f5e-4ff7-85f2-8df5df5e2b2e/MyAPI");
//options.UserOptions.RoleClaim = "appRole";
});
await builder.Build().RunAsync();
}
In my API, I just have the Authorise attribute set on the class, and eventually will need roles in there too:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class CarController
Then, in my Blazor component, I then inject the http factory and try to make a request:
#inject IHttpClientFactory _factory
...
private async Task RetrieveCars()
{
var httpClient = _factory.CreateClient("APIClient");
HttpResponseMessage response = await httpClient.GetAsync("https://localhost:11001/api/cars");
var resp = await response.Content.ReadAsStringAsync();
cars = JsonSerializer.Deserialize<List<Car>>(resp);
}
but this returns the 401 error. I've also tried a few different variations like just injecting a http client (#inject HttpClient Http) but nothing seems to be adding my authorisation into the API calls. The options.UserOptions.RoleClaim is also commented out in the AddMsalAuthentication section as I wasn't sure if it was needed, but it doesn't work with or without it in there.
Can anyone explain what I'm doing wrong and what code I should be using?
Common causes.
Most cases ,we tend to forget to grant consent after giving API
permissions in the app registration portal,after exposing the api
which may lead to unauthorized error.
Other thing is when Audience doesn’t match the “aud” claim when we
track the token in jwt.io .Make sure ,Audience=clientId is configured
in the code in authentication scheme or Token validation parameters
by giving ValidAudiences.And also try with and without api:// prefix
in client id parameter.
Sometimes aud claim doesn’t match as we mistakenly send ID token
instead of Access tokens as access tokens are meant to call APIs .So
make sure you check mark both ID Token and access token in portal
while app registration.
While Enabling the authentication by injecting the [Authorize]
attribute to the Razor pages.Also add reference
Microsoft.AspNetCore.Authorization as(#using
Microsoft.AspNetCore.Authorization)
Please see the note in MS docs and some common-errors
If above are not the cases, please provide with additional error details and startup configurations or any link that you are following to investigate further.
I am using Identity Server 4 and I need to interact with external authorization API.
The authorization process must be like that:
Client sends generated token based on user's data to IdentityServer
IdentityServer creates POST request with specific header and body.
IdentityServer send this request to ExternalAuthApi and gets a response containing token
IdentityServer returning that token to the Client (and caching it)
I looked through docs about External Identity Provider, but it requires interaction between Client and ExternalAuthApi in some way, which I need to avoid.
How to implement direct interaction between IdentityServer and ExternalAuthApi? Is it possible?
I've achieved this using ITokenCreationService.
I've created my own implementation and added to services: services.AddTransient<ITokenCreationService, ProviderBasedTokenCreationService >();
3rd party services are now called in CreateTokenAsync like
public override async Task<string> CreateTokenAsync(Token token)
{
var provider = token.GetProviderClaim();
switch (provider)
{
case "3rdPartySystem_A":
return this._systemAClient.RequestToken(token.TransformToSystemAFormat());
case "anotherSystem":
return this._anotherSystemClient.RequestToken(token.TransformToAnotherSystemFormat());
default:
return await base.CreateTokenAsync(token);
}
}
I just wanted to know how can we validate the azure ad access token in a backend API in my case i.e. Django rest framework.
Consider that I have a single page app or a native app and a backend API (django rest framework) completely independen of each other. In my case if my single page app/native app wants to access certain data from the backend API, and inorder to access the API, user should be logged in the backend API.
So what my approch is to make use of MSAL library to get the access token from the SPA/native app and then once token is acquired, pass that token to backend API, validate it, get the user info from graph api. If user exists in the DB then login the user and pass the required info. If user info doesn't exist then create the user, login and pass the info from the API.
So my question is when I pass the access token to my backend api, how can I validate that the token that a user/SPA/native app has passed to backend API is valid token or not?
Is it just we need to make an API call to graph API endpoint with accessToken that user/SPA/native passed and if it is able to get the user data with the accessToken then then token is valid or if it fails then the accessToken is invalid.
Is it the general way to validate the token or some better approach is there? Please help
Good day sir, I wanna share some of my ideas here and I know it's not a solution but it's too long for a comment.
I created a SPA before which used msal.js to make users sign in and generate access token to call graph api, you must know here that when you generate the access token you need to set the scope of the target api, for example, you wanna call 'graph.microsoft.com/v1.0/me', you need a token with the scope 'User.Read, User.ReadWrite' and you also need to add delegated api permission to the azure app.
So as the custom api of your own backend program. I created a springboot api which will return 'hello world' if I call 'localhost:8080/hello', if I wanna my api protected by azure ad, I need to add a filter to validate all the request if has a valid access token. So I need to find a jwt library to decode the token in request head and check if it has a token, if the token has expired and whether the token has the correct scope. So here, which scope is the correct scope? It's decided by the api you exposed in azure ad. You can set the scope named like 'AA_Custom_Impression', and then you can add this delegate api permission to the client azure ad app, then you that app to generate an access token with the scope of 'AA_Custom_Impression'. After appending the Bearer token in calling request, it will be filtered by backend code.
I don't know about python, so I can just recommend you this sample, you may try it, it's provided by microsoft.
I've solved the similar issue. I don't found how to directly validate access token, but you can just call graph API on backend with token you've got on client side with MSAL.
Node.js example:
class Microsoft {
get baseUrl() {
return 'https://graph.microsoft.com/v1.0'
}
async getUserProfile(accessToken) {
const response = await got(`${this.baseUrl}/me`, {
headers: {
'x-li-format': 'json',
Authorization: `Bearer ${accessToken}`,
},
json: true,
})
return response.body
}
// `acessToken` - passed from client
async authorize(accessToken) {
try {
const userProfile = await this.getUserProfile(accessToken)
const email = userProfile.userPrincipalName
// Note: not every MS account has email, so additional validation may be required
const user = await db.users.findOne({ email })
if (user) {
// login logic
} else {
// create user logic
}
} catch (error) {
// If request to graph API fails we know that token wrong or not enough permissions. `error` object may be additionally parsed to get relevant error message. See https://learn.microsoft.com/en-us/graph/errors
throw new Error('401 (Unauthorized)')
}
}
}
Yes we can validate the Azure AD Bearer token.
You can fellow up below link,
https://github.com/odwyersoftware/azure-ad-verify-token
https://pypi.org/project/azure-ad-verify-token/
We can use this for both Django and flask.
You can directly install using pip
but I'm not sure in Django. If Django install working failed then try to copy paste the code from GitHub
Validation steps this library makes:
1. Accepts an Azure AD B2C JWT.
Bearer token
2. Extracts `kid` from unverified headers.
kid from **Bearer token**
3. Finds `kid` within Azure JWKS.
KID from list of kid from this link `https://login.microsoftonline.com/{tenantid}/discovery/v2.0/keys`
4. Obtains RSA key from JWK.
5. Calls `jwt.decode` with necessary parameters, which inturn validates:
- Signature
- Expiration
- Audience
- Issuer
- Key
- Algorithm
I am using IdeneityServer4 and i have another Web Api controller in the identityserver4 api project and it is protected using identityserver 4 [Authorize(LocalApi.PolicyName)]. part of the authorization/authentication process, i need to call another web api (which does not exist in this identityserve4 api project) endpoint using IHttpClientFactory in .net core. i am getting 401 unauthorized error. if i call this different end point by passing a token it works but if i call it from the identityserver4 api it is not working. basically i am trying to do is
1. client (angular app) calls the identityserver4 api by passing credentials and get a token. (IT WoRKS)
2. client calls another end point (different web api controller) in identityserver4 api by passing token (IT WORKS).
in this end point i am trying to call another user notification api by passing the same token received and it is failing with 401. the notification api is also using the same identityserver4 for authorization.
3. if i just call the notification api separately by passing the token it works fine.
i do not see any examples calling another endpoint from identity server api. i see different web api controllers in the identity server 4 api
I am getting the token using following code in the web api controller method Identityserver 4 api and passing that token and required params to the SendUserInvitation method and its failing.
string bearerToken = null;
if (Request != null && Request.Headers.ContainsKey("Authorization"))
{
bearerToken = Request.Headers["Authorization"].FirstOrDefault().Replace("Bearer ", "");
}
======================================================================
public static async Task<bool> SendUserInvitation(string endpoint, string bearerToken, User userDetails, IHttpClientFactory _clientFactory)
{
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Add("Authorization", bearerToken);
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(userDetails));
var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
request.Content = httpContent;
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
return !string.IsNullOrEmpty(responseContent) ? true : false;
}
not sure why i am getting 401, the same token being passed to different endpoint but still getting 401.