Request types:
Statuscodes:
REST stands for Representational State Transfer. It’s an architectural style used for designing networked applications, especially web services. REST relies on a client-server architecture where the client interacts with the server via requests to access and manipulate resources on the server
Cache-Control
header field)URI’s are hierarchical structures that represent a resource on which CRUD actions can be performed.
hierarchical:
http://www.myservice.org/discussions/{year}/{day}/{month}/{topic}
It is also possible to add parameters to the URI to filter or sort on
a resources:
http://www.myservice.org/discussions?region=USA&year=2023
POST <openSlotRequest
)POST <appointmentRequest
)GET ?date=20231224
)200 OK <openSlotList
)Why springboot?
Spring avoids a lot of manual configuration and is easy to manage dependently.
import org.springframework.web.bind.annotation.*;
@RestController // Indicate that this class is a RestController
@RequestMapping("agenda") //The path to access agendas (eg: localhost:8080/agenda)
public class AgendaController {
private Agenda agenda;
AgendaController(Agenda agenda) { this.agenda = agenda; }
pubic
@GetMapping // Indicate that method handles Get-requests
public Collection<AgendaItem> agendaFromNow() {
return agenda.getAgendaItem(new Date());
}
}
@RestController
:
servlet
( a class that handles requests,
processes them and reply back with a response)@RequestMapping
:
@GetMapping
:
GetMapping("/id/{id}")
)
@RequestParam("id")
@PostMapping
:
@RequestBody AgendaItem item
DI is needed to make the Agenda class (dependency) available to the
to RestController
.
@Component
public class Agenda {...}
A REST-method typically returns a ResponseEntity
(along
with a message).
@GetMapping("/age")
<String> age(@RequestParam("yearOfBirth") int yearOfBirth) {
ResponseEntityif (invalidAge(yearOfBirth)) {
return new ResponseEntity<>("Invalid year of birth", HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("YOB is " + yearOfBirth, HttpStatus.OK);
}
It is also possible to couple a statuscode to an exception:
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
class CustomException extends RuntimeException {}
@PostMapping()
@ResponseStatus(HttpStatus.CREATED)
public void storeEmployee(@RequestBody Employee employee) {...}
Here the ResponseStatus
will automatically return a 201
if the creation of employee was successful.
Finally it is also common to respond with a URI.
@PostMapping("/user/{username}")
public ResponseEntity<Void> createTodo(@PathVariable String username, @RequestBody Todo todo) {
.setUsername(username);
todo= toJpaRepo.save(todo);
Todo createdTodo URI uri = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/id").buildAndExpand(createdTodo.getId()).toUri();
return ResponseEntity.created(uri).build();
}
Fixture:
Four phases:
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
@ActiveProfile("test")
class RestAgendaApplicationTest {
@Autowired
private WebTestClient webClient;
@Test
public void testGetAllRepos() {
.get().uri("/api/repos")
WebTestClient.accept(MediaType.APPLICATION_JSON_UTF8) // Set header
.exchange() // Send message
.expectStatus().isOk() // Check status
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) // Check header
.expectedBodyList(Repo.class) // Check body
}
}
The @ActiveProfile
annotation is optional but is very
useful. It let’s us use a different bean configured specifically for
testing purposes.
@Component
@Profile("test")
public class DummyDao implements Dao {...}
Configure login information
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
= new InMemoryUserDetailsManager();
InMemoryUserDetailsManager manager .createUser(User.withUsername("user")
manager.password(bCryptPasswordEncoder.encode("userPass"))
.roles("USER")
.build());
.createUser(User.withUsername("admin")
manager.password(bCryptPasswordEncoder.encode("adminPass"))
.roles("USER", "ADMIN")
.build());
return manager;
}
...
}
Assign roles to users and restrict certain roles from performing certain actions.
Allow certain roles webservice:
(anyRequest()
)
Allow certain roles on some paths:
(requestMatchers(path)
)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasRole("USER")
.anyRequest().authenticated()
);
return http.build();
}
...
}
Allow certain roles on some webservice methods:
(@EnableMethodSecurity()
)
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
@RolesAllowed("ROLE_VIEWER")
public String getUser();
@RolesAllowed({ "ROLE_ADMIN", "ROLE_EDITOR" })
public void deleteUser();
}
Request | Method | |
---|---|---|
Type | Rough | Detailed |
Place of config | In config class | on specific method |
Type of config | With lambda expression | With annotations |
Definition of auth | In code | Spring Expression Language |
CSRF is an attack where an attacker tricks a user’s browser into making an unintended and unauthorized request to a different website, exploiting the user’s authenticated session.
How to defend against CSRF?
Make distinction between real and fake HTTP-request by using a Synchronizer Token Pattern or SameSite-attribute cookie.
Both methods generate a token for the client on login. Then every time the client interacts with the server, the client includes that token in its request. On every request, the server will validate if it received a valid token. These tokens have a certain expiration date to avoid problems upon token theft.
💡: In Spring, CSRF security is enabled by default.
ORM is a programming technique that allows devs to work with objects in the code while transparently persisting and retrieving data from a relational database. It allows devs to define how objects/classes map to database tables and vice versa. Each object corresponds to a table in the database.
There are a lot of concepts in OOP that resembles the structure of Relational databases: - Identities - Relations - Inheritance
There are a couple requirements to be able to map an object to a DB.
Using the JPA (Java Persistence API). Every dataclass must be a Java Bean.
@Entity
@Table(name = "sportclub")
public class Sportclub {
private Long id;
private String name;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
There are a couple different strategies to assign a new Primary Key:
The JpaRepository
is an interface that is a
CRUD-repository with extra functionalities for performing database
operations.
// Typically defined in a single file (like ProductRepository.java)
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByCategory(String category); // Optional custom user-defined queries
}
Example usage of build-in queries:
// ProductDao.java
@Service
public class ProductDao{
private final ProductRepository repo;
public ProductDao(ProductRepository repository) {
this.repo = repository;
}
public List<Product> getProductsByCategory(String category) {
return repo.findByCategory(category);
}
public Product getProductById(long id) {
return repo.findById(id).orElse(null);
}
public void addProduct(Product p) {
.save(addProduct);
repo}
}
List of build-in methods:
save
: save new objectfindAll
: return all entitiesfindAllById
: return all with certain idflush
: write changes to DBsaveAll
: Save list of objectsdelete
, deleteAll
: remove
entity/entitiesexistById
: does entity exist?⚠️: Using a JPA requires some configuration in
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/dudosdb?serverTimezone=UTC
spring.datasource.username=TLuca
spring.datasource.password=<redacted>
There are different annotation to indicate what type of inheritance an object is subject to:
@Inheritance(strategy = ...)
: Sets how entities are
stored in the DB (default = SINGLE_TABLE
)@DiscriminatorColumn(name = "...", discriminatorType= ...)
:
Defines a column that distinguishes entity types in a single tableInheritance strategy:
@DiscriminatorValue("K")
: Assigns a specific value to
differentiate an entity type in a single table inheritance setup@PrimaryKeyJoinColumn(name = "...")
: Maps a parent
table’s primary key as a foreign key in the child table in joined table
inheritance.Associations:
Value-objects criteria:
It doesn’t make sense to save the value-object (Address
)
in a separate table. So we can store the Address
in the
same table in multiple Embedded columns
.
We would also like to make an association to a separate table containing all the email addresses.
@Embeddable
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
// Getters, setters, constructors, etc.
}
@Entity
@Table(name = "sportclub")
public class sportclub {
@Id
private Long id;
private String name;
@Embedded
private Address address;
@ElementCollection
@CollectionTable(name = "emailaddresses",
= @JoinColumn(name = "sportclub"))
joinColumns @Column(name="email")
}
Different mappings of associations of entities:
@OneToOne
: Defines a one-to-one relationship between
two entities.@JoinColumn
: Specifies the column used for joining two
entities in a one-to-one relationship.@OneToMany
: Represents a one-to-many relationship
between two entities.@JoinColumn
(within @OneToMany
): Specifies
the column in the “many” side entity to join with the “one” side
entity.@ManyToOne
: Specifies a many-to-one relationship
between two entities.@JoinColumn
(within @ManyToOne
): Specifies
the column in the “many” side entity that references the “one” side
entity.@ManyToMany
: Defines a many-to-many relationship
between two entities.@JoinTable
: Specifies the join table for the
many-to-many relationship, defining the columns and their mappings.For detailed examples of relations (with annotation parameters) see: https://www.baeldung.com/jpa-hibernate-associations
❗: TODO
Node.js is a JS runtime environment that runs on the V8 JavaScript engine.
let http = require('http');
let fs = require('fs');
let port = process.env.port || 8080;
.createServer( (req, res) => {
httpconsole.log("Request on port 8080");
.writeHead(200, {
res'Content-Type': 'text/html',
'Access-Control-Allow-Origin': '*'
;
})let read = fs.createReadStream(__dirname + '/index.html');
.pipe(res);
read.listen(port); })
server.js
let server = require('./start');
let router = require('./router');
let requestHandlers = require("./handlers");
let handler = {};
"/"] = requestHandlers.home;
handler["/show"] = requestHandlers.show;
handler["/upload"] = requestHandlers.upload;
handler[
.start(router.route, handler); server
handlers.js
let fs = require('fs');
function home(res) {
respond(res,'views/home.html');
return true;
}
function show(response) {...}
function upload(response) {...}
function respond(response, file) {
.readFile(file, (err, data) => {
fs.writeHead(200, {"Content-Type": "text/html"});
response.write(data);
response.end();
response;
})
}.home = home;
exports.show = show;
exports.upload = upload; exports
router.js
function route(pathname, handler, response) {
console.log("Request for " + pathname);
if (handler[pathname] !== undefined) {
return handler[pathname](response);
else {
} console.log("No Method found for " + pathname);
return null;
}
}.route = route; exports
start.js
let http = require("http");
let url = require("url");
function start(route, handler) {
console.log("Starting.");
.js
start
function onRequest(request, response) {
let pathname = url.parse(request.url).pathname;
let content = route(pathname, handler, response);
if (!content) {
.writeHead(404, {"Content-Type": "text/plain"});
response.write("404 Not found");
response.end();
response
}
}
let port = process.env.port || 8080;
.createServer(onRequest).listen(port);
httpconsole.log("Has been started.");
}
.start = start; exports
setTimeout()
or setInterval()
setImmediate()
callbackssocket.on('close',...)
)⚠️: Because of Node.js concurrency model, it’s important to never block the event loop, instead, use callbacks and asynchronous methods.
Express is a framework for Node.js that makes it easier to build RESTful APIs. A significant part of Express revolves around middleware.
app.js
let express = require('express') // Module
let path = require('path')
let logger = require('morgan')
let bodyParser = require('body-parser')
let routes = require('./routes/index')
let users = require('./routes/users')
let app = express()
//Views
.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app// Middleware
.use(logger('dev'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app// Routes
.use('/', routes);
app.use('/users', users);
app
// error handler
.use( (err, req, res, next) => {
app.locals.message = err.message;
res.locals.error =
res.app.get('env') === 'development' ? err : {};
req.status(err.status || 500);
res.render('error');
res;
})
.exports = app; module
routes/index.js
= require('express');
let express = express.Router();
let router
.get('/', (req, res, next) => {
router.render('index', { title: 'Express' });
res});
.exports = router; module
WS client example:
let ws = new WebSocket("ws://www.websockets.org")
// Open connection
.addEventListener('open', (event) => { ws.send("Hello server!") })
ws// Listen for incoming messages
.addEventListener('message', (event) => { console.log(event.data) })
ws// Send a message
.send("Hello")
ws// Server closes connection
.addEventListener('close', (event) => { alert("closed") })
ws// Client closes connection
.close() ws
Before websockets, clients used polling
to achieve the
same functionality. Websockets, however, have less latency and less
overhead which makes the way more efficient.
Streams allow for requested data to be send as it comes. Parts of the data can already be send while other parts have not been loaded into memory yet. This is done by creating a pipeline that subscribes to any incoming data, processes the data, and then publishes it.
Traditional methods required the application to store requests in memory. If the size of the required data is larger than the available memory, an “out of memory” error will occur.
Central to reactive programming are observables (or streams) that emit/push values or events and observers (or subscribers) that react to these emissions. Observers can subscribe to observables to receive and handle emitted values asynchronously.
To efficiently program reactive applications, the reactor library is often used.
A Flux
is used to emit multiple values (asynchronously)
over time.
A Mono
is a kind of flux that can send only one element
or none at all.
Reactive Stack | Servlet Stack |
---|---|
Spring security reactive | Spring Security |
Spring WebFlux | Spring MVC |
Spring reactive repos: Mongo, Redis, … | Spring data repos: JDBC, JPA, NoSQL |
Reactive DAO
@Service
public class SampleDAO{
final Random random = new Random();
String[] ids = {"S1", "S2", "S3"};
}
public Flux<Sample> getSamples() {
return Flux.interval(Duration.ofSeconds(1)).take(10)
.map(pulse -> getSample());
}
private Sample getSample() {
= new Sample();
Sample s .setId(ids[random.nextInt(ids.length)]);
s.setTime(LocalDateTime.now());
s.setValue(4882.42);
sreturn s;
}
}
Reactive controller
@RestController
@RequestMapping("samples")
public class SampleController {
private SampleDAO dao;
public SampleController(SampleDAO dao) { this.dao = dao; }
@GetMapping("sample")
public Flux<Sample> getSamples() { return dao.getSamples(); }
}
Reactive client
public class ClientTestWebflux {
public void testServer() {
= WebClient.create("http://localhost:8080");
WebClient client <Sample> sampleFlux = client.get()
Flux.uri("/samples")
.retrieve()
.bodyToFlux(Sample.class);
.subscribe(System.out::println);
sampleFluxSystem.out.println("in test webflux");
}
}
db_constants.properties
# info databank
username=TLuca
password=<redacted>
driver=org.postgresql.Driver
connectionstring=jdbc:postgresql://localhost:5432/db_name
#Constanten for table products
Q_select_products=select * from products
Q_select_product=select * from products where productCode = ?
# Mappings to column names
prod_code=productCode
prod_name=productName
prod_price=buyPrice
prod_msrp=MSRP
@Component
@PropertySource("classpath:databankconstanten.properties")
public class JDBCDataStorage implements IDataStorage {
@Value("${connectionstring}")
private String connString;
@Value("${username}")
private String login;
@Value("${password}")
private String password;
// Columns
@Value("${prod_code}")
private String prodCode;
@Value("${prod_name}")
private String prodName;
@Value("${prod_price}")
private String prodPrice;
@Value("${prod_msrp}")
private String prodMsrp;
// SQL-queries
@Value("${Q_select_products}")
private String selectProducts;
private DataSource dataSource;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private Connection openConnectie() throws SQLException {
return dataSource.getConnection();
}
}
public List<IProduct> getProducts() throws DataException { // DataException is custom class
List<IProduct> products;
try (Connection conn = openConnectie(); Statement stmt = conn.createStatement()) {
System.out.println(conn.getClass());
System.out.println(stmt.getClass());
ResultSet rs = stmt.executeQuery(selectProducts);
System.out.println(rs.getClass());
= new ArrayList<>();
products while (rs.next()) {
.add(createProduct(rs));
products}
} catch (SQLException ex) {
throw new DataExceptie(foutProducts);
}
return products;
}
When using prepared statements, we possibly expose the application to SQL-injection. We can defend ourself against this vulnerability by working with parameters:
String username = getUserInput();
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
.setString(1, username);
preparedStatementResultSet resultSet = preparedStatement.executeQuery();
DataSource:
To get access from .NET-application to datasource (database, xml, …).
A DataSet
is a collection of data tables that contain
the data. It is used to fetch data without interacting with a Data
Source. It is an in-memory data store that can hold more than one table
at the same time.
// Creating a DataSet
= new DataSet();
DataSet ds = new DataSet("CustomerOrders");
DataSet customerDS
// Creating a Table
= customerDS.Tables.Add("Orders");
DataTable orders
// Creating column
= orders.Comumns.Add("OrderID", typeof(Int32));
DataColumn orderId
// Key constraint
.PrimaryKey = new DataColumn[] {orderId};
orders
// Adding data to rows
= orders.NewRow();
DataRow rowOrders [bestelID] = 3;
rowOrders["Quantity"] = 9;
rowOrders.Rows.Add(rowOrders); orders
Connection
: Connect to DBCommand
: Perform a commandDataReader
: Obtain dataDataAdapter
: Connect DataSet with DBOrdersDAOReader.cs
// Configuration
= ConfigurtionManager.AppSettings["SELECT_ORDER"];
String task = ConfigurationManager.ConnectionString["orders"];
ConnectionStringSettings connStringSet
// Factory for provider
= DbProviderFactory.GetFactory(connStringSet.ProviderName);
DbProviderFactory factory
// Connection object
= factory.CreateConnection();
DbConnection conn = conn.CreateCommand();
DbCommand command
.CommandText = Configuration.AppSettings["SELECT_ORDERS"]
command
// Run command-objects
using (DbConnection conn = ...) {
.Open()
conn// delete-, insert-, update queries
int rowsChanged = command.ExecuteNonQuery();
// select queries
= command.ExecuteReader();
DbDataReader reader while (reader.Read()) {
= tableOrder.NewRow();
DataRow row [0] = reader["orderID"];
row[1] = reader[1];
row[2] = reader.GetString(2);
row.Rows.Add(row);
tabelOrder}
}
App.config
configuration>
<connectionStrings>
<add name="orders" providerName="System.Data.SqlClient"
< connectionString="Data Source=(LocalDB)\MSSQLLocalDB"/>
connectionStrings>
</appSettings>
<add key="SELECT_ORDER" value="select * from Orders"/>
<appSettings>
</configuration> </
The adapter can add data to the DataTable
, and write
those changes to the DB. Before, the changes in DataTable
where not definitive.
DataAdapter
has 4 Command-objects:
SelectCommand
: Retrieves data from the database based
on specified criteria, usually in the form of a SELECT queryInsertCommand
: Inserts new data into the database. It
typically includes an INSERT queryUpdateCommand
: Modifies existing data in the database.
(Write changes made to DataSource
to DB)DeleteCommand
: Removes data from the databaseOrderDAOAddapter.cs
= factory.CreateDataAdapter();
DbDataAdapter adapter = ...;
DbCommand command .SelectCommand = ...;
adapter= new DataSet(DS_ORDERS);
DataSet customerDS .Fill(customerDS, TABLE_ORDERS); adapter
using (DbConnection conn = ...) {
.Open();
conn
// Basic transaction
using (DbTransaction trans = conn.BeginTransaction())
{
try
{
// Perform database operations within the transaction
// Commit the transaction
.Commit();
trans} catch (Exception) {
// Rollback the transaction if an exception occurs
.Rollback();
trans}
}
}
The .NET alternative to the Spring framework for REST-services is ASP.NET. It makes it possible to easily create a HTTP service that exposes an application’s functionality and data as services over the internet using standard HTTP methods (GET, POST, PUT, DELETE, etc.) and data formats like JSON or XML.
Controllers/PrimateController.cs
[Route("api/ape_species")]
[ApiController]
public class PrimateController : ControllerBase {
private readonly PrimateContext _context;
public PrimateController(PrimateSpeciesContext ctx) {
= ctx;
_context if(_context.PrimateSpecies.Count() == 0) {
.PrimateSpecies.Add(new PrimateSpecies { Name = "Black Spider Monkey"});
_context.SaveChanges();
_context}
}
[HttpGet]
public IEnumerable<string> Get() {...}
[HttpGet("{id}")]
public async Task<ActionResult<PrimateSpecies> GetSpeciesById(long id) {
var species = await _context.PrimateSpeciesCollection.FindAsync(id);
if (species == null) { return NotFound(); }
return species;
}
[HttpPost]
public async Task<ActionResult<PrimateSpecies>> PostPrimate(PrimateSpecies species) {
.PrimateSpeciesCollection.Add(species);
_context.SaveChangesAsync();
await _contextreturn CreatedAtAction("GetSpeciesById", new { id = species.Id}, species);
}
[HttpDelete("{id}")]
public async Task<ActionResult<PrimateSpecies>> DeletePrimateSpecies(long id) {
var species = await _context.PrimateSpeciesCollection.FindAsync(id);
if (species == null) { return NotFound(); }
.SapienSpeciesCollection.Remove(species);
_context.SaveChangesAsync();
await _contextreturn species;
}
}
Modes/DbContext.cs
public class PrimateSpeciesContext: DbContext {
public PrimateSpeciesContext(DbContextOptions<PrimateSpeciesContext> options) : base(options) { }
public DbSet<PrimateSpecies> SapienSpeciesCollection { get; set; }
}
Models/PrimateSpecies.cs
.Models {
nanespace vbWebAPIpublic class PrimateSpecies {
public long Id { get; set; }
[Required]
public string Name {get; set; }
[DefaultValue(false)]
public bool IsComplete { get; set; }
}
}
JSON is easy to automatically generate JSON using swagger.io.
💡: Swagger can also interpret comments in code like
javadocs
MVC’s aim to separate concerns within an application by dividing it into three interconnected components:
Structure:
Application
┣━ /Controllers/
┃ ┗ ProductController.cs
┣━ /Models/
┃ ┗ ProductModel.cs
┣━ /Views/
┃ ┗ /Product/
┃ ┣ Index.cshtml
┃ ┗ Details.cshtml
┗━ Program.cs
// Product model class representing the data structure
public class Product
{
public int Id { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "Name can not be empty.")]
public string Name { get; set; }
public decimal Price { get; set; }
}
View()
: Opens view with name matching the methodView("String")
: Opens view
string.cshtml
View(Object)
: proved Object to view called
<methodname>.cshtml
View("string", Object)
: provide Object to view called
string.cshtml
<Product>
@model List<ul>
foreach (var product in Model)
@{
<li>@product.Name - $@product.Price</li>
}
</ul>
IActionResult
return the View that matches the method
name, but can also:
public class ProductController : Controller
{
public IActionResult Index()
{
// Simulated data retrieval
var products = new List<Product>
{
new Product { Id = 1, Name = "Product A", Price = 19.99M },
new Product { Id = 2, Name = "Product B", Price = 29.99M },
new Product { Id = 3, Name = "Product C", Price = 39.99M }
};
return View(products); // Pass the list of products to the view
}
// /Product/Detail/{id}
public IActionResult Details(int id)
{
return View(id);
}
// /Product/Details?id={id}
public IActionResult Details() {
return View(Convert.ToInt32(Request["id"]));
}
}
Program.cs:
.MapControllerRoute(name: "blog",
app: "blog/{*article}",
pattern: new { controller = "Blog", action = "Article" });
defaults.MapControllerRoute(name: "default",
app: "{controller=Home}/{action=Index}/{id?}"); pattern
⚠️: It’s also important that the app is protected against XSRF/CSRF (Cross-site request forgery): attacker exploits a user’s authenticated session to execute malicious actions. To prevent this, tokens or verification techniques are used to confirm the authenticity of requests, ensuring they originate from the expected user.
// Only add storage if user had valid identity
.Services.AddDefaultIdentity<IdentityUser>(options =>
builder.SignIn.RequireConfirmedAccount = true)
options.AddEntityFrameworkStores<ApplicationDbContext>();
...
.UseAuthentication();
app.UseAuthorization(); app
public class AccountController : Controller {
[AllowAnonymous] // Allow anyone
public ActionResult Login(){ }
[Authorize] // Allow only to authorized users
public ActionResult Logout(){ }
[Authorize(Roles = "Administrator")]
public ActionResult AdminPanel() {}
}
Parties:
Flow:
Types of Grants:
OAuth vulnerabilities:
Authentication involves multiple steps. Possible over multiple devices.
The injection of SQL into ordinary input fields/parameters (XSS)
Bad implementation of authentication and session management. Attacker gets access to passwords, keys or session tokens.
Sensitive data not sufficiently protected because of bad or old cryptography implementation.
💡: Use existing, well respected frameworks to implement cryptography.
Bad design, not tested against common vulnerabilities.
Users have to many access rights (attacker gets right to other accounts, files, databases, …)
Using an insecure server to gain access to intern data (eg: urls as parameter in response).
Using libraries of untrusted sources, or having an unsafe CI/CD pipeline.
Newer version often contain patches for 0-days.