Reputation: 519
I am wanting to be able to access the UserInfo endpoint /connect/userinfo
but when I do so it says 401... implying authentication needed. So I ran into this article that talks about how we can implement it in .net code in order to be able to access the UserInfo Endpoint. So inside of my TokenServices.cs
file I did the following however, I am not sure how I am able to get the token itself. I do have a method GetToken()
that will retrieve a token but I am not sure if I can use that method for the line that sets Token = token
.
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
This is the full class file:
namespace WeatherMVC.Services
{
public class TokenService : ITokenService
{
private readonly ILogger<TokenService> _logger;
private readonly IOptions<IdentityServerSettings> _identityServerSettings;
private readonly DiscoveryDocumentResponse _discoveryDocument;
public TokenService(ILogger<TokenService> logger, IOptions<IdentityServerSettings> identityServerSettings)
{
_logger = logger;
_identityServerSettings = identityServerSettings;
using var httpClient = new HttpClient();
_discoveryDocument = httpClient.GetDiscoveryDocumentAsync(identityServerSettings.Value.DiscoveryUrl).Result;
if (_discoveryDocument.IsError)
{
logger.LogError($"Unable to get discovery document. Error is: {_discoveryDocument.Error}");
throw new Exception("Unable to get discovery document", _discoveryDocument.Exception);
}
}
public async Task<TokenResponse> GetToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = _discoveryDocument.TokenEndpoint,
ClientId = _identityServerSettings.Value.ClientName,
ClientSecret = _identityServerSettings.Value.ClientPassword,
Scope = scope
});
if (tokenResponse.IsError)
{
_logger.LogError($"Unable to get token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get token", tokenResponse.Exception);
}
return tokenResponse;
}
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
}
Inside of my HomeController
I have:
namespace WeatherMVC.Controllers
{
public class HomeController : Controller
{
private readonly ITokenService _tokenService;
private readonly ILogger<HomeController> _logger;
public HomeController(ITokenService tokenService, ILogger<HomeController> logger)
{
_tokenService = tokenService;
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[Authorize] // 25:44 in youtube video
public async Task<IActionResult> Weather()
{
var data = new List<WeatherData>();
using (var client = new HttpClient())
{
var tokenResponse = await _tokenService.GetToken("weatherapi.read");
client.SetBearerToken(tokenResponse.AccessToken);
var result = client
.GetAsync("https://localhost:5445/weatherforecast")
.Result;
if (result.IsSuccessStatusCode)
{
var model = result.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject<List<WeatherData>>(model);
return View(data);
}
else
{
throw new Exception("Unable to get content");
}
}
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["InteractiveServiceSettings:AuthorityUrl"];
options.ClientId = Configuration["InteractiveServiceSettings:ClientId"];
options.ClientSecret = Configuration["InteractiveServiceSettings:ClientSecret"];
options.ResponseType = "code";
options.UsePkce = true;
options.ResponseMode = "query";
options.Scope.Add(Configuration["InteractiveServiceSettings:Scopes:0"]);
options.SaveTokens = true;
});
services.Configure<IdentityServerSettings>(Configuration.GetSection("IdentityServerSettings"));
services.AddSingleton<ITokenService, TokenService>();
}
WeatherApi Project
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication("Bearer", options =>
{
options.ApiName = "weatherapi";
options.Authority = "https://localhost:5443";
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "weatherapi", Version = "v1" });
});
}
Identity project
namespace identity
{
public static class Config
{
public static List<TestUser> Users
{
get
{
var address = new
{
street_address = "One Hacker Way",
locality = "Heidelberg",
postal_code = 69118,
country = "Germany"
};
return new List<TestUser>
{
new TestUser
{
SubjectId = "818727",
Username = "alice",
Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "admin"),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser
{
SubjectId = "88421113",
Username = "bob",
Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "user"),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
}
};
}
}
public static IEnumerable<IdentityResource> IdentityResources =>
new[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new[]
{
new ApiScope("weatherapi.read"),
new ApiScope("weatherapi.write"),
};
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource("weatherapi")
{
Scopes = new List<string> {"weatherapi.read", "weatherapi.write"},
ApiSecrets = new List<Secret> {new Secret("ScopeSecret".Sha256())},
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<Client> Clients =>
new[]
{
// m2m client credentials flow client
new Client
{
ClientId = "m2m.client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedScopes = {"weatherapi.read", "weatherapi.write"}
},
// interactive client using code flow + pkce
new Client
{
ClientId = "interactive",
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {"https://localhost:5444/signin-oidc" , "https://localhost:44394/signin-oidc"},
FrontChannelLogoutUri = "https://localhost:5444/signout-oidc",
PostLogoutRedirectUris = {"https://localhost:5444/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "weatherapi.read"},
RequirePkce = true,
RequireConsent = true,
AllowPlainTextPkce = false
},
};
}
}
Is there a specific way I can call the token so I can set Token to that UserInfo token? Any pointers/suggestions would be greatly appreciated!
UPDATED
To access this screen (HomeController -> Weather()
) I have to be authorized and it keeps me logged in when I access this page regardless how long the bearer token says it lasts. So why can't I access the /connect/userinfo
page?
Upvotes: 0
Views: 2812
Reputation: 19941
The use of options.SaveTokens = true; (In AddOpenIDConnect) will save all the tokens in the user cookie and then you can access it using:
var accessToken = await HttpContext.GetTokenAsync("access_token");
Sample code to get all the tokens if provided:
ViewBag.access_token = HttpContext.GetTokenAsync("access_token").Result;
ViewBag.id_token = HttpContext.GetTokenAsync("id_token").Result;
ViewBag.refresh_token = HttpContext.GetTokenAsync("refresh_token").Result;
ViewBag.token_type = HttpContext.GetTokenAsync("token_type").Result; //Bearer
ViewBag.expires_at = HttpContext.GetTokenAsync("expires_at").Result; // "2021-02-01T10:58:28.0000000+00:00"
Sample code to make a request
var accessToken = await HttpContext.GetTokenAsync("access_token");
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
var client = new HttpClient();
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Authorization = authheader;
var content = await client.GetStringAsync("https://localhost:7001/api/payment");
ViewBag.Json = JObject.Parse(content).ToString();
return View();
Upvotes: 2