How to integrate swagger with Azure Active Directory OAuth - c#

I'm trying to setup Swagger in my AspNetCore 2.1 application using Azure Active Directory V2 but I cannot seem to get it right. I am able to configure the setup so that swagger prompts, redirects and successfully authenticates my client/user but when passing the bearer token to the server results in the error Bearer error="invalid_token", error_description="The signature is invalid". I have created a GitHub repository with the project I am trying to get work with all its configuration (https://github.com/alucard112/auth-problem)
I have managed to get the V1 endpoint working, by setting the resource to the Client Id of the AAD app, which results in the JWT token having the 'aud' set to the app client Id. In the V2 endpoint the 'aud' is being set to what I think is the Graph API resource '00000003-0000-0000-c000-000000000000'. I believe this is my problem at the moment, although am not 100 % sure. The V2 endpoints don't seem to have a way to define the audience like the V1 did unless of course there is some oversight from my side.
My Startup file is structured as follows:
The authentication is setup as the following:
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority = $"https://login.microsoftonline.com/{tenantId}";
options.TokenValidationParameters = new TokenValidationParameters
{
// In multi-tenant apps you should disable issuer validation:
ValidateIssuer = false,
// In case you want to allow only specific tenants,
// you can set the ValidIssuers property to a list of valid issuer ids
// or specify a delegate for the IssuerValidator property, e.g.
// IssuerValidator = (issuer, token, parameters) => {}
// the validator should return the issuer string
// if it is valid and throw an exception if not
};
});
And the swagger is setup as follows:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "Protected Api",
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
//IMATE - StevensW
// Define the OAuth2.0 scheme that's in use (i.e. Implicit Flow)
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
TokenUrl = $"https://login.microsoftonline.com/common/{tenantId}/v2.0/token",
Scopes = new Dictionary<string, string>
{
{ "openid", "Unsure" },
{ "profile", "Also Unsure" }
}
});
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.OAuthClientId(Configuration.GetValue<string>("AzureAd:ClientId"));
c.OAuthAppName("Protected API");
// c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
// NEVER set the client secret here. It will ve exposed in the html of the swagger page if you "view source" and its not needed for OpenID Auth
// c.OAuthClientSecret(Configuration.GetValue<string>("AzureAd:ClientId"));
});
I am hoping to configure the swagger UI to use AAD's V2 endpoint and allow for a multi-tenant login that allows successfully authenticated API calls to be executed. Any help or direction would be greatly appreciated.

I ended up fixing the problem I was having. Working through this post helped me understand my mistakes.
The first mistake was my actual AAD app registration. I had not set a scope for the application under "Expose an API". Because they deprecated the resource property in V2, the way you would set the resource was to create a scope with the format api"//{application ID}/{scope_name}. After I made this change my AAD application was now correctly configured.
After that, I needed to add an additional section to my startup file:
return services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
// This is an Azure AD v2.0 Web API
options.Authority += "/v2.0";
// The valid audiences are both the Client ID (options.Audience) and api://{ClientID}
options.TokenValidationParameters.ValidAudiences = new string[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = false;
});
Note: the link above provided an alternative solution to turning off the validation of the issuer if anyone is interested.
My AppSettings file was also simplified by only needing to define the Instance, TenantId, and ClientId.
Then from a swagger perspective, I just needed to add an additional scope to the security definition matching the one I created in my AAD application.
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
TokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
Scopes = new Dictionary<string, string>
{
{ "openid", "Sign In Permissions" },
{ "profile", "User Profile Permissions" },
{ $"api://{clientId}/access_as_user", "Application API Permissions" }
}
});
After these changes my application is now working as expected.

for v2 endpoint, update the accessTokenAcceptedVersion in Manifest of AAD from null to 2. It will work.

Related

Authorize Google analytics data from a web application

I am trying to authorize an ASP.NET Core 6 MVC web app to Google analytics data API.
[GoogleScopedAuthorize("https://www.googleapis.com/auth/analytics.readonly")]
public async Task<IActionResult> Index([FromServices] IGoogleAuthProvider auth)
{
var cred = await auth.GetCredentialAsync();
var client = await BetaAnalyticsDataClient.CreateAsync(CancellationToken.None);
var request = new RunReportRequest
{
Property = "properties/" + XXXXX,
Dimensions = {new Dimension {Name = "date"},},
Metrics = {new Metric {Name = "totalUsers"},new Metric {Name = "newUsers"}},
DateRanges = {new DateRange {StartDate = "2021-04-01", EndDate = "today"},},
};
var response = await client.RunReportAsync(request);
}
The authorization goes though as would be expected; I am getting an access token back.
I cant seem to figure out how to apply the credentials to the BetaAnalyticsDataClient.
When I run it without applying it to the BetaAnalyticsDataClient, I get the following error:
InvalidOperationException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
I am not currently using GOOGLE_APPLICATION_CREDENTIALS as it is configured in programs.cs. I don't see the need to have client id and secret configured in program.cs plus having an added env var.
Why isn't it just picking up the authorization already supplied with the controller runs?
builder.Services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = builder.Configuration["Google:ClientId"];
options.ClientSecret = builder.Configuration["Google:ClientSecret"];
});
Is there an alternate method for authorizing with a web app that I have not been able to find. I did do some dinging in the source code I can't seem to find a method to apply this.
After quite a bit of digging i managed to find that it was possible to create my own client builder and apply the credentials there.
var clientBuilder = new BetaAnalyticsDataClientBuilder()
{
Credential = await auth.GetCredentialAsync()
};
var client = await clientBuilder.BuildAsync();
Hope this helps someone else.

Azure Active Directory login returns the signature key was not found

I have implemented authentication for my ASP.NET Core 3.0 Web API using Azure AD. When I use the [Authorize] attribute, I am getting a http 401 error response with the message
Bearer error="invalid_token", error_description="The signature key was not found"
My current ConfigureService() in Startup.cs looks like this:
options.AddSecurityDefinition("oauth", new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
TokenUrl = new Uri($"https://login.microsoftonline.com/<mytenantid>/oauth2/v2.0/token"),
AuthorizationUrl = new Uri($"https://login.microsoftonline.com/TenantId/oauth2/v2.0/authorize", UriKind.RelativeOrAbsolute),
Scopes = { { "api://<myappid>/user_impersonation", "user_impersonation" } }
}
}
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Authority = $"https://login.microsoftonline.com/<mytenantid>/v2.0";
o.Audience = "<myappid>";
o.TokenValidationParameters.ValidAudiences = new string[] { "<myappid>", $"api://<myappid>" };
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
Please let me know if I am missing something
I have answered many similar questions before. According to your question, you expose the web api protected by Azure. Next, you need to create a client application and use the client application to call the api application.
Usually the 401 error means that the audience of your token does not match your api. When you use the token to call the api, you will receive a 401 unauthorized error. The access token is issued based on the audience, so you must Make sure to set the scope to your api when you request the token. Of course you can also parse the token, check the aud claim, and make sure it is the api you want to call.
I use the auth code flow to do a simple demonstration for you:
First expose the api of the api application and add the client application.
Next,under 'API permissions', give your front-end application access to your back-end api application:
Under 'API permissions' click on 'Add permission', then click on the 'My APIs' tab.
Find your backend application and select the appropriate scope.
Click 'Add permissions'.
Grant admin consent for your APIs.
Get token:
Parse the token:

OpenIddict authorization request not handled

I'm trying out OpenIddict 3.0 for use in a SSO app. I followed the steps in the documentation, created an Authorize controller, and added a test application. When I try to connect to authorize I get this exception:
System.InvalidOperationException: The authorization request was not handled. To handle authorization requests, create a class implementing 'IOpenIddictServerHandler' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.
Alternatively, enable the pass-through mode to handle them at a later stage.
I can't find anything in the documentation or sample apps that explains what this means. What am I missing?
Here's my code so far. In Startup.cs:
services.AddOpenIddict()
.AddCore(o =>
{
o.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
})
.AddServer(o =>
{
o.SetTokenEndpointUris("/connect/token");
o.SetAuthorizationEndpointUris("/connect/authorize");
o.AllowAuthorizationCodeFlow();
o.RegisterScopes(OpenIddictConstants.Scopes.Email);
o.AcceptAnonymousClients();
o.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
o.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
})
.AddValidation(o =>
{
o.UseLocalServer();
o.UseAspNetCore();
});
And test app description:
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "test-app",
DisplayName = "Test Application",
PostLogoutRedirectUris = { new Uri("https://oidcdebugger.com/debug") },
RedirectUris = { new Uri("https://oidcdebugger.com/debug") }
};
I'm testing with the OpenID Connect debugger.
To handle authorization requests in a MVC controller, you must tell OpenIddict's ASP.NET Core host to use the pass-through mode, exactly like what you did for the token endpoint:
services.AddOpenIddict()
.AddServer(options =>
{
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough() // Add this line.
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
});

How to include Microsoft graph in scopes?

I am working on Azure AD and .net core application. I have a swagger application which will do authentication and authorization. Below is my swagger config.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = swaggerUIOptions.AuthorizationUrl,
TokenUrl = swaggerUIOptions.TokenUrl,
Scopes = new Dictionary<string, string>
{
{ "Read", "https://graph.microsoft.com/user.read" }
}
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "readAccess", "writeAccess" } }
});
});
Below is my config in Configure.
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger";
c.OAuthClientId(swaggerUIOptions.ClientId);
c.OAuthClientSecret(swaggerUIOptions.ClientSecret);
c.OAuthRealm(azureActiveDirectoryOptions.ClientId);
c.OAuthAppName("Swagger");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
Below are my AuthorizationUrl and TokenUrl
"AuthorizationUrl": "https://login.microsoftonline.com/tenantid/oauth2/v2.0/authorize",
"TokenUrl": "https://login.microsoftonline.com/tenantid/oauth2/v2.0/token"
Below is API permission set in azure ad.
When I try to get token(I selected scope checkmark) in swagger I get below error.
The application swagger asked for Read that doesn't exist on the resource
Also, I have one more app for back end APIs. When we try to get access token, can we the same token to call Graph APIs and backend APIs?
The application swagger asked for Read that doesn't exist on the
resource.
There is no Read permission under Microsoft Graph api. You can use User.Read or https://graph.microsoft.com/User.Read
Also, I have one more app for back end APIs. When we try to get access
token, can we the same token to call Graph APIs and backend APIs?
You can use this token to call Graph APIs, but need to add the permissions for the api. You can not use the same token to call different apis under different resources.

How add two different tokens in ASP.NET Core Web API

I need the Authorize attribute in our Controller can accept two different tokens.
One token, is provided from one private ADFS, and other token is provided from AzureAd.
Several Ionic clients go to over ADFS, other Ionic clients go to over Azure AD
My dev scenario: ASP.NET Core 2.2 Web API
My actual startup.cs (abbreviated)
ConfigureService()
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer((options =>
{
options.Audience = Configuration["Adfs:Audience"];
options.Authority = Configuration["Adfs:Issuer"];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
}));
}
I need here the other Authentication with AzureAD. How?
The Configure method of Startup.cs
Configure(…)
{
app.UseAuthentication()
}
With this code, only can access the ADFS Token and this users, can obtains result from the controllers. However, the AzureAD user's can't obtain access
I don't know how make this code for double token authorization, and our controllers can response if one token is from ADFS or other token is from AzureAD
You can set multiple JWT Bearer Authentication with different schema name :
services.AddAuthentication()
.AddJwtBearer("ADFS",options =>
{
options.Audience = Configuration["Adfs:Audience"];
options.Authority = Configuration["Adfs:Issuer"];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
})
.AddJwtBearer("AAD", options =>
{
//AAD jwt validation configuration
});
If you want to make your controller/action to accept two jwt tokens , tokens from AAD or ADFS are ok to access your controller/action , you can make a policy to let both the AAD and ADFS authentication schemes tried to authenticate the request :
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AAD", "ADFS")
.Build();
});
In addition , if you want to know which schema the token is from , you can check the particular claim in user's identity , or directly add authentication schema value to user claims in events :
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnTokenValidated = (context) =>
{
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("schema", "AAD"));
return Task.FromResult(0);
}
};
And get in action after authentication :
var result = User.Claims.Where(c=>c.Type=="schema").FirstOrDefault().Value;

Categories